Tuesday, December 9, 2008

Day 332: Hindsight

In retrospect, it would have been a good idea to make the "have you completed this mission" system a little more unit-test friendly. More on that in a moment.

Every fleet the game is going to have for part I is done. You may have noticed how I keep talking about parts of the game and whatnot. Why is this? Well, the plot's in three parts. How will the player know? That's the other major ticket I completed today: Banner states. Simply put, this scrolls things (probably words) upward so the player can read them, so I can now create all the "PART II: The Revenge" scrolling goodness I want. I've already implemented the game's credits screen this way.

I also started making the random missions for my game. In any coding project, I'm always a bit wary of returning to some functionality I haven't used in a while, for fear it'd broken somehow in the meanwhile. As superstitious as this sounds, it happened to be the truth this time around: My junk was broke. Some of this was changes I'd made in the meantime, and some of it was the fact that parts never worked correctly in the first place.

For instance, random missions. My testing of this involved generating one and then flying around and making sure it worked. That was fine. What I neglected to do Way Back When I first made the functionality was test it any further. It turns out, when a random mission generator makes more than one mission, it tacked on the conditions for every other mission it generates. Oops.

Tuesday, December 2, 2008

Day 325: Label Randomly

Yesterday I took a well-deserved rest.

Today I figured I'd take an easy ticket, the one whereby I make random missions accept labels. As though welcoming me back to coding my game, this was far more difficult than I'd wanted it to be.

The first step was to adapt the KuiAtCondition to also respond to labels. As it is, you give it a list of destinations (either planets, organizations, or sectors) and if the player is there, it passes. It seemed simple to extend this for labels....

It's never easy.

For one, only the root KuiObject knows how to handle labels, and I'd hardcoded that in. My choices were: Refactor labeling out to a more generic module, or copy and paste the code. For once, I did the responsible thing and refactored. It took an hour!

The refactoring turned out to be worthwhile, as it happened. I needed to do similar label handling in at least two other places later, so as usual, it paid for itself.

Still, it's never easy!

Sunday, November 30, 2008

Day 323: Game Over

Today marks the last day of National Game Finishing Month. I closed five tickets today, mostly content-related. Every ship that's in part1 of the game is now created. Every weapon that's in the game entirely is done. Even helpful standard addons have been made.

In conclusion, of the list I made yesterday, half of the items are now done. So now, the NaGaFiMo postmortem!

  • What went right

    1. Effort The spirit of this exercise was to devote my Nanowrimo time to the game. I've done more than that - as of today, I'm nearly 4.5 hours ahead of schedule. If it were a novel, it'd be done by now.

    2. Learning While I won't say that I haven't learned anything from the rest of the programming I've done, I've definitely learned more this month. Specifically, I had to create new art via Blender, which I knew how to do long ago but have since forgotten. Making art is definitely out of my comfort zone, but I learned quite a bit.

    3. Atomic Coding The linked article talks about the practice with Subversion, but I've found it even more effective using git. Having a local repository lets you commit nearly everything once you've made the smallest workable change, yet not embarrass yourself by pushing out to the remote (public!) repository.



  • What Went Wrong

    1. It's Not Done! The goal of this was to actually have the game finished by now. That didn't happen, but given that I actually put in all the work, I count this as a failure of estimation rather than of effort.

    2. Learning Yes, that was above on the 'what went right' portion. The downside to learning how to use a tool like Blender is that I had to take time away from the rest of the project in order to do it. If I'd already known blender or already done all this, I'd have ended up a lot closer to the original goal.

    3. Going Dark Though I updated the blog when I was done with major features, I didn't update daily. As I've found the blog to be useful in the past, I'd like to keep it up.



  • What I learned

    1. This is Doable This particular schedule works for me. I initially thought 1.5 hours a day might be somewhat difficult, but though not easy, it is at least workable.

    2. Get it Done Rather than putting off seemingly unrelated work until it absolutely must be done, it's probably better to learn these things when it won't interfere with big goals

    3. People Might Actually Play This I've had a number of requests for the demo, which while being exceedingly out of date is still a reasonable facsimile of how the game plays. My novels have had only a few token readers, with the vast majority of people who inquire not bothering to actually read them. Everyone who asked for the demo has played it however, making me think that I may have better luck!



Saturday, November 29, 2008

Day 322: The End is Nigh

National Game Finishing Month is nearly over. Tomorrow I plan to do a marathon of getting stuff into the system then blog resignedly about it. Today, however, was all work.

Specifically, I animated the rest of the ships. Then I created the standard ones in-game, and used Mass Distribution to push them out to every standard planet in the galaxy.

High profile things left to do:

  • Random Missions should use labels: The mission system in general could use an overhaul to use the labels, too. It'd be handy for an "are we there yet" check to include "anywhere with this label" as a satisfying condition.

  • Fleets: Once I have the ships done, I need to put the patrols, pirates, traders, visitors, etc, in their place so they'll show up in the sectors they're supposed to.

  • Alliance/Rebel weaponry: The war-fighters should have better weapons than you as a regular pilot should get (but you can get them later, when you join)

  • Alliance Ships: The alliance has three ships specific to them.

  • Rebel Ships: Likewise, the rebellion also has ships just for themselves.

  • Standard Addons: I don't think I'll have faction-specific addons unless I really get inspired.

  • Random Missions: These are essentially the filler of the game.

  • Scientist Scout Missions: This is the essense of the plot for the entire game, right here.

  • Alliance/Rebel Scout Missions: The only thing close to a major sidequest, this is the initial alliance/rebel war.

Wednesday, November 26, 2008

Day 319: Blockified

Every single time I do graphics for anything, I have to re-teach myself Blender, which I'm actually somewhat decent at. It turns out my wife has an eye for what would make spaceships look even better, so I've created quite a few. I should now have every ship I need.

I rendered all 36 frames of one of the ships before remembering that I had no way to change the 36 individual .png files into the one giant .png file that the engine could handle. I've written this script easily twice before (once for TraderMissions, and once more for a pyweek competition). But I couldn't locate the first script, and the second one wasn't suited to my current engine. So I had to, once again, write the script.

It was an easy task, for once. blockify.rb is a pretty brittle utility without a lot of other uses, but it gets the job done and hopefully I won't have to re-write it next time I need it!

Sunday, November 23, 2008

Day 316: Weapons of Ordinary Destruction

I fixed an important bug today: In the old system, changing an object's tag after it had been created was a bad idea. The game would simply crash upon reloading, if not sooner. The new system exchanged this fatal bug for a more subtle one: Re-naming caused duplicates to exist in the repository (the old name stuck around).

This sounds trivial to fix - just see if there's an old tag and delete it. However, objects which are duplicated (like, say, weapons) always have the old tag of the object they were created from. So any new weapons were destroying the old instances.

But can't I check to see if they're being duped? Yes, I can - now. Most of my coding work today was enabling exactly that. So now you can change an object's tag, and unless you change it from object_thing to object_thing:4490, you're fine.

Hint: Don't do that.

Finally, I created every weapon I wanted to be available to the player at the start of the game, and distributed them to all the planets where I wanted them to be available.

Unfortunately, I used up all of the weapon art I had in TraderMissions, thus meaning I'm going to have to create new stuff soon. Re-learning Blender, ahoy!

Saturday, November 22, 2008

Day 315: How not to use Mass Distribution

The next big thing on my plate was to create the various cargos that the player could buy and sell. This was aided by the fact that I'd already diagrammed out how much I wanted the low,high,and medium versions of those cargos to cost.

I started with 'Food'. It's cheap on Earth because of the lots of landmass. So I created a cheap version of food, and moved on.

It then occurred to me that this was going to take /forever/. I'd have to create 3 states for each of the different cargos I wanted to sell (and as of now there are 6). Also there'd be no consistency between prices. A low 'food' price could be half the price of medium food, while low specimens might end up half a buck off.

"Wouldn't it be nice", I thought, "To be able to automate this in the editor somehow?"

Yes, yes it would. And since I'm the guy who wrote the editor, I did it. There are now 'auto low', 'auto medium', and 'auto high' buttons in the cargo editor, which set the values and names of the cargo to 15% lower, the same, or 15% higher than the base price, respectively.

This would still require me to create all 18 cargos. Wouldn't it be nice, I thought, if when you create a cargo blueprint it automatically created the low/med/hi versions of itself?

Yes, yes it would. And so again, I did it.

Here's where my brilliant plan fell down. The planets hadn't been tagged for specific cargo. So while a number of planets shared the feature that their food was cheap, I hadn't tagged them to take advantage of that and use the MD system to do all of this at once. I had two alternatives:

  1. Go through all the planets and re-tag them

  2. Go through all the planets and just add all the cargo I wanted


As these were equivalently labor intensive, I did the second. That's when I ran into another problem: When the cargos are automatically created as outlined above, they're just tossed into the repository. The repository stores everything in a gigantic dictionary, which of course means they're unsorted. So I still end up looking at a list of 18 different cargo items, all in arbitrary order.

Wouldn't it be nice, I thought, if those were sorted?

Yes, yes it would be. Done!

I again reiterate that, as frustrated as I've been in the past by editors for other games, it's enormously liberating to actually be able to do something about it :)

Sunday, November 16, 2008

Day 309: Mass Distribution

The Mass Distribution (or, as I shall refer to it from now on because I'm already tired of typing the whole thing, 'MD') system took me a few days, and then it was done.

To recap: The idea behind MD was that I could label objects (say, sectors) in a certain way, and then distribute objects (say, fleets) to aforementioned sectors. So if I'd created a trading fleet, I could put it in every inhabited sector at once rather than one-by-one going through each sector and adding the fleet manually.

The only thing this really required was some GUI work (the actual adding of the objects was, as it turns out, ridiculously easy), but it required something I hadn't created before: Namely, a way to find any object in the entire universe. Thus was born:


THE OMNICHOOSER


On the top-left, all the model classes in the game. When selected, the results show every object of that type. The top-right features every label in the game, and when it's chosen it'll show every object with any of the selected labels. When both are active, it shows all objects with any of the selected labels of the given type. For my example above, I picked "KuiFleet" and "traders" to get the trading fleet I'd just mentioned. There's another one of these for the 'destination' objects (i.e. all the sectors in the universe) - there I chose "KuiSector" and "inhabited". A few button clicks later and I'd placed 20-some-odd fleets.

What this means is that I'm likely to return to creating actual content soon. I haven't kept the blog updated because I've been doing the same thing every day ("Look, listboxes are broken! Look, the OmniChooser is broken! Look, both are broken at once!") but hopefully now the MD will speed everything up and I'll have more to say.

Sunday, November 9, 2008

Day 302: RTFS

I finished Phase II of the great XML change yesterday. I would have posted then, but there was testing to be done to make sure it was Actually Fixed This Time, Dammit. I'm discouraged by the number of bugs that this change uncovered in my code - that is, not bugs in the new code I wrote, but code I wrote months ago and has been working fine since. This includes bugs of the "How did this ever work in the first place?" variety.

But that's all fixed, at least until I discover the bugs in this code a few months from now.

I've been trying to use XSLT as my patch tool of choice, and so far so good. Only I found a bug where it wouldn't preserve linefeeds in attributes, which was important because a lot of my descriptions ended up being like this:

<sector name="foo" description="Name.

Here is some other stuff about this
sector. That linefeed's important
because I don't want to go back and
manually put linefeeds where they
should have been."/>

No problem, I think, because I'm still somewhat new to the XSLT scene there's probably something I missed. Hours of all the whitespace-preserving options I can find, and no luck. Finally, I find the answer.

The coding gurus of Stackoverflow were the lucky ones to inform me: It's not a bug. Not preserving whitespace in the attributes is actually part of the XML Spec.

The only way I got away with it for as long as I did was because Ruby's XML parser was ignoring this fact. So I wrote a ruby script to do the transformation. I felt somewhat relieved that it wasn't actually my fault things weren't working out, there was a bug in REXML. Then I realized that, if I hadn't broken the spec to begin with, I wouldn't be in this mess.

Thus the Programming Tip of the Day: Even when it's not your fault, it's probably your fault.

Saturday, November 1, 2008

Day 294: NaGaFiMo

It's National Game Finishing Month!

My next big part of the game to do is mess with the XML some more - specifically, making it look sane as I advocated a few entries ago. But that would require me to muck with XML again and after the nightmare it was last time, I declined to do so.

Instead, I closed out some old improvements I'd been meaning to do for a while:

  • Ownership: The player will now see what faction his/her target belongs to.

  • Fuel Regeneration: By default, fuel will now (extremely) slowly regenerate now. So if you're stuck out in the middle of nowhere with no fuel, you can just wait. It'd probably still be faster to save your game and change your ship's 'fuel' value in the XML, but now people less inclined toward cheating can continue to play.

  • Description Comments: Sometimes I want to remember something about a planet or sector, i.e. "This is the place where Act I ends". There's no place for comments, so instead of taking the easy route and making a do-nothing 'comments' field, I made it so any string surrounded by /* ... */ would not appear if the game wasn't in edit mode. Instant comments!



I've got to get to that XML at some point, though....

Friday, October 31, 2008

Day 293: No Nano

Lately I haven't been hewing to my 'one hour per day' quota. At all. Whatsoever. And suddenly, hey, it's November and I'm looking at two months until my deadline for releasing this wacky thing. November is, as I've alluded to before, National Novel Writing Month, and I've wanted to participate this year.

THAT ISN'T HAPPENING!

Instead, I'm dedicating my NaNo time to working on this game - an hour and a half per day, every day. My goal is to actually finish everything by the end of the month, giving me a month to hand over the beta to people and watch as they rain bug reports upon my poor undefended soul.

I started a day early - I began phase 1 of my XML overhauls quite a while back. I was suffering from "arrow XML", which looked a lot like this:

<object>
<child name="foo">
<other_object>
<child name="bar">
<yet_another_object/>
</child>
</other_object>
</child>
</object>

Now imagine it nested about twenty levels deep.

It seemed like it'd be fairly straightforward to write each object out one by one and then just refer to them by ID, but - as it turned out - it wasn't. Changing the way my parser worked exposed some deep bugs in the system that I hadn't seen before, and I was forced to quash them!

Now, however, it works. Thus, National Game Finishing Month begins!

Tuesday, October 14, 2008

Day 276: Rebel Yell

Much like the last blog, much work was done this time around on the planets. As you might deduce from the title, this time around I did all the rebels. What this means is that I'm done with planets....

Which pretty much means I'm done with normal content creation altogether. I'm going to have to go ahead with my earlier mass-distribution system, which means lots of coding. I'm not entirely sure how I feel about that. On the one hand, it was always less tedious to code than make new content for the game. On the other hand, making new content was a very casual thing, I could just sit down and work for a bit. We'll see how it turns out.

Other things include me re-thinking the XML I'm doing. Here's what it looks like now:

<sector x=5 y=7 ...>
<child name="planets">
<planet .../>
</child>
</sector>

There's a lot of stuff being replaced by those ellipses. And I mean a /lot/. Multi-line text fields are the most common. I'd like to merge them as their own elements. So it'd be something like this:

<sector tag="foo">
<fields>
<x>5</x>
<y>7</y>
</fields>
<children>
<planets>
<planet/>
</planets>
</children>
</sector>

It's more verbose, but I also think it's an easier read. At least, when I'm debugging, it's a pain in the ass to try to locate the one field I need in a sea of text. This way they're at least on their own line.

Converting between the two will be... interesting. I think I'll start a data-format versioning system so new versions of the game will be able to understand old/differently formatted data, or at least reject it out of hand. Then I'll use the magic of XSLT to change everything I've written so far from the old format to the new.

I just learned XSLT recently, and I'm liking it. It may end up being my patch tool of choice!

Tuesday, October 7, 2008

Day 269: Alliance

Oh, my lacking blogging/development schedule.

Today I finished up every single sector of the Alliance by tagging and placing planets where they belong. And very little - if any - code was written in the process.

As evidenced by this rather short entry, I have a great deal less to say when I'm making content for the game then when I'm coding.

Ah well. Entry post go!

Monday, September 15, 2008

Day 247: Express Yourself

As mentioned yesterday, in the Bad Old Days (i.e. those before today) you had to name something and then give it a descriptive tag which was often just the thing name re-branded. Well those days are over!

The 'auto' button now takes the name and creates a tag for it. So:

Name: The Best Player in the World is from a City in an Arboretum

Becomes:

Tag: player_best_player_world_city_arboretum

So the tag is based on the type of object, plus a scaled down version of the name. As you no doubt noticed, I took out a bunch of connector words there (mainly articles). This involved regular expressions.

At first, I was doing something like this:

name.gsub!(/\sthe\s/,' ')
name.gsub!(/\sa\s/,' ')
name.gsub!(/\san\s/,' ')

Yes, I had one line for each thing I wanted to remove, which is of course silly. So I made a list of all the excluded words and tried again, only I ran into a problem. It's not a string I'm using for matching here, it's a regular expression - meaning I couldn't just throw the current loop value in the middle and expect it to work!

Luckily, there's a way to do exactly that:

exclusions.each do |x|
excludeMiddle = Regexp.new("\s#{x}\s")
name.gsub!(excludeMiddle,' ')
end


Regexp.new takes a string, from which it'll create a regular expression. And I can do all the manipulating I want within strings!

Also I made a few more planets using the new autotagging feature. But that's not as exciting.

Sunday, September 14, 2008

Day 246: Checking In

This time, I've been half working, half slacking. Though I did take a few days off, I also continue to face the fact that "Made some planets and tagged them" remains a very boring blog entry. So I'm trying to split my time up even further - 30 to 45 minutes for the planets, then 15 to whatever else I feel like doing for one of my many features I haven't yet implemented.

Today's feature was putting an 'auto' button next to the 'tag' input. So now, instead of naming something "Monster Island" and then having to click down to 'tag' and type "planet_monster_island", you can now just hit the 'auto' button and it'll come up with that for you. Well, it would if it worked - right now it just says "You clicked the auto button!"

Features I did previous to today include accidentally creating a planet I didn't mean to and then realizing that I had no way to delete planets. You'd think I'd have learned my lesson after making the exact same mistake with Sectors.

Sunday, September 7, 2008

Day 239: Mass Effect

I started this blog because I saw a number of my fellow developers making dev-blogs for their projects. I had two thoughts:

  1. I won't have anything to blog about: This was the primary reason I haven't blogged in the past, I'm just not that interesting. In this case, though, I can blog often because this is a daily project. Lately, as you've no doubt noticed, this practice has fallen by the wayside. I've continued to work, but writing up the blog post takes a lot more time and so I've tended to skip it.

  2. Nobody will read it: How I'm developing my game is of little interest to anyone except pretty hardcore game developers who are familiar with ruby, rubygame, and interested in my game enough to read about it instead of working on their own. This set is maybe three people, five tops. I've since discovered that having the blog is invaluable - I've searched through it a number of times to find how I did something earlier, and the fact that I blogged about it made it simple to look up. Even if no other living soul even glances at this blog, it's already been of enormous use.


So that said, the slow pace of blogging may continue. When I'm writing code, I often have something to say about how I implemented it. When I'm developing content, what I have to say is pretty much "Created a few sectors".

Now, however, I'm coding. I created a ton of sectors and a few planets, and it occurred to me that later, when I've made a "standard array" of ships I want the player to be able to buy, I'm going to have to go back to every single planet I made and put those ships up for sale there.

The upside to being the author of a game editor is that if you don't like how it's doing something, you can change it. Today I began work on the "Mass Distribution" system. Each object has labels (like tags, only I call them 'labels' because I'm already using 'tag') that group it, and the Mass Distribution system will allow you to take a ship, a weapon, a mission, etc, and give it to everything that shares a label. So if I, while creating these new planets, label them something like "alliance, standard, human", I can go back and easily give the standard array of weapons,ships, etc, to all the planets which share whatever tags I want.

I may modify the random mission generator to use this technology as well.

Sunday, August 31, 2008

Day 232: Re-re-port

It's been a while since I blogged but unlike last time that happened, I've actually been busy in the meantime. Here's what I've been up to:

  • Ported everything to new-style events. This was fairly easy and straightforward

  • The player can now give their pilot a name. Eventually this will be rolled into savegames, so you can have more than one pilot per module

  • I fixed a Rubygame bug where if your application was minimized and lost focus at the same time, it'd crash.

  • A freaking ton of map work! I have to say that creating maps is a lot easier with an editor. Coming up with ideas for a ton of sectors, however, remains difficult. I made the mistake of giving my initial generic filler sector a description, and so now have to go out of my way to explain how boring the other dozen filler sectors are.


The map descriptions I've written already depict a lot of lore pre-game. There was a time where humanity didn't know hyperspace lanes could go to empty sectors, for instance. After that, the Sandwall spurred the invention of shielding. There was an alien invasion scare at some point. All these are completely divorced from the plot, just things I've come up with as I made the map.

I was asking myself the question: Should I write this down somewhere so I remember it? Then it occurred to me: I just did!

Wednesday, August 27, 2008

Day 228: Demo!

So today I fixed a bug. It was less a bug and something I didn't realize was missing. I mis-clicked while making the map for the new scenario, and ended up creating a sector I didn't need. That's when I realized the map editor didn't have a 'delete' button!

What an accomplishment! I'm totally spent.

Oh, yeah, and I made a demo.

DEMO!!!!!!!!!!

That's right! I loaded Fedora 9 onto my VM and tested the file I just linked to right there. On a pristine Fedora 9 load, it works with no issues. It also runs on Ubuntu! It even runs on both at the same time! On the VMs it chugged down a bit during the combat, which says to me that I've still got more optimization to do, but it may also have been due to the fact that I was running it on a VM while I was also running it on another VM.

That link may not be permanent. It's my home server, so be gentle :)

Tuesday, August 26, 2008

Day 227: Execute!

My current build does not work with the current Rubygame/dev-2.4 branch due to one of my patches having not yet made its way in. It'll die the moment an AI-controlled ship attempts to figure out how to move. This will hopefully change soon.

This should hopefully not matter; the demo itself is looking even more likely to come out this week. Rubyscript2exe is everything I'd ever hoped for and more! Mainly because it will use whatever libraries I have on my system, including my already-patched version of Rubygame. I unintstalled Rubygame from my system entirely, as a matter of fact, and the executable still ran. I'm waiting until I can get it to run on a pristine Fedora VM, and then I'm releasing the demo to you, the public.

Rake can now make .tgz files out of the entire distribution (actually, it could always do that, but I only just figured out how today). This makes my life even easier, because it means I can use rake to build the executable and then put it in a .tgz file for distribution in one step!

Finally, it turns out that Rubygame 2.4, which I'm now targeting as I can't live without my mousewheel support, has the new event classes that will one day be part of 3.0 and which I just finished moving all my code away from. Luckily, I knew I'd have to switch back and made a giant list a few posts ago about what I changed. And I did, in fact, tag the last commit before changing over in git, so a (very) careful merge may turn out to be the easy answer.

Then again, I seem to remember that it's never easy!

Monday, August 25, 2008

Day 226: The Coming Storm

Instead of doing all that releasey stuff I mentioned yesterday, I instead started creating the map for the module I intend to include with the game when it ships.

It was yesterday, actually, that I started writing up the outline, and three hours later it was included; those who don't want the plot spoiled for them should avoid poking around too deeply in the Github repository.

Oh, did I mention? Kuiper's on Github now. It's nice having a backup of my work that's not just another machine in my house. Right now if you want to play the game, you'll have to use Rubygame's 2.4-dev branch, and to be perfectly honest I haven't exactly tried downloading it and integrating with it. That's a task for Soon(tm).

Anyway, the point of the post is that the new module is called, creatively enough, "The Coming Storm". The short description is:

Humanity is alone among the stars and, as always when humanity has only itself to deal with, war is constant. An inner circle of systems are allied against separatist rebels who seek their independence. It would be a tale told a thousand times, if there were not much worse in store....


Yeah? Seemed like I was going to go with a typical empire vs. rebels thing there, but then I promised it totally wouldn't go down that way! Remember that when you're playing Part I and being all like "This is a typical empire vs. rebels thing!"

Sunday, August 24, 2008

Day 225: More Returning

Getting back into the swing of things is difficult. Sundays are great for working on the game because I feel pressure to make something out of my weekend and time's running out; every other day... not so much.

That said, milestone:Tutorial is complete. Sometime soon, hopefully this week, I'm going to look at RubyScript2Exe, see if I can get it integrated into Rake, and have a 'preview' available. I've started to set up a few VirtualBox VMs for testing. Likely the preview release will be linux-only, unless I get very very inspired to build it on windows.

I'm at a critical juncture for this game, because it was right around exactly this point that I stopped developing for TraderMissions. The engine was there, I'd written a tutorial that worked perfectly, I was all ready to create my new universe, and then my enthusiasm for the project vanished entirely.

I'm hoping that won't happen this time. One of the big differences between TM and Kuiper is that Kuiper has a built-in editor. A lesson I learned from TM was that I don't especially enjoy creating content when I could be programming. Hopefully the editor will streamline these efforts for me.

While we're hoping, let's hope I continue working on this and you can all see a blog from me tomorrow!

Sunday, August 17, 2008

Day 218: Re-port

Today's major task was to port Kuiper from the old 3.0 codebase to the currently developed Rubygame 2.4. This seems to have gone off without any problems, with the exception that I'd made a number of changes to the 3.0 codebase (mouse wheel support, etc) that hadn't been backported yet.

Well, I'm not a Rubygame developer for no reason, so I went and backported them myself. As alluded to earlier, I'm starting to use Git, and it turns out that you don't even have to be an official developer to make changes to Rubygame if you're going the Git route. Instead, you fork the github Rubygame project, make changes to that, and send a 'pull request' back. If you didn't understand any of what I just said, feel better knowing that I didn't really either. I'll have to wait and see if I did it right :)

So on that list, three of the four things I mentioned I wanted to do have mostly been done. I just need to make a little more balance for the tutorial, and then I'm doing a preview release!

The following are the notes I made to myself on the porting process, so later when I need to re-port the game to Rubygame 3.0, I remember what I had to change. You can safely ignore these:


  • Had to change the screen portion of engine to use 'or's instead of an array

  • Put support in 2.4 for key repeats

  • Put support in 2.4 for mouse wheel

  • Fixed bug in ftor new_from_to

  • text.rb keyTyped used symbols, changed to Rubygame::K_*

  • Options.rb, change symbols there to Rubygame::K_*

  • Clock.wait uses milliseconds, Clock.tick returns milliseconds

  • mouseUp in state used symbols, replace with Rubygame::*

  • Change emergency hooks from syms to Rubygame::K_*

  • Change unit tests to use constants instead of syms

  • Map::update used syms for arrow-key movement

Saturday, August 16, 2008

Day 217: Extended Rest

The subject is a reference to D&D 4th edition. A normal extended rest lasts about 6-8 hours. In my case, it was nearly two months!

But I have resumed. It turns out that optimizing code is fairly soul-crushing. The flipside of "Premature optimization is the root of all evil" is today's Programming Tip of the Day:


It's not premature optimization if it's actually time to optimize

But if you've been approaching development the entire time with the attitude that you don't need to optimize, it becomes quite an adjustment to go back to the problem parts and realize how horrible your code actually was.

I want to post this before midnight, so I'm not going to go into a lot of detail on the fixes. Most involved caching a value rather than recalculating it, something I know is faster but won't do unless the situation really calls for it, as I've been bitten in the ass before by out-of-date cache data. I replaced a linear search of an array that happened every frame with a dictionary lookup, which provided the expected results.

So on one hand, not optimizing early is a good idea. On the other hand, you have to pay for it later when the time comes. On the gripping hand, the code's so poor that I really had nowhere to go but up!

Thursday, June 19, 2008

Day 159: Profiling

Today was my first in-depth profiling. Yesterday I did some looking over, but even with ruby-prof's excellent HTML output, it was taking me a while to get my head around exactly where the bottlenecks in my code were.

Then I found out that ruby-prof can output in a format that KCacheGrind understands. I also found out that KCacheGrind exists, which was likewise helpful, and suddenly I had a tool which could walk me through parts that needed optimizing.

My first surprise was that Set::merge was called FOUR MILLION TIMES. No, I'm not making that number up. When you ask a KuiObject for its attributes, children, etc, it collects all of the ones it knows about in a set and then merges it with the ones its superclass knows about. Except there was no caching here, it was doing it every single time.

Even that wouldn't have been a problem, because you'd think I wouldn't be asking what every single attribute of an object is except when I'm loading/saving (and then time is not as important). But it turns out that it was heavily used in comparison. Two KuiObjects are == if all their children, attributes, etc, are equal, and this requires me to call Set::merge above.

So I fixed the caching problem, and that sped things up. Then I made it so that two KuiObjects are == if their tags are ==. This changed a dozen+ string comparisons and countless object comparisons into one string comparison. That sped things up a lot.

The beauty of using profiling is that I can tell whether harebrained ideas I have for optimization actually work. For instance, I thought that the string comparisons were likely to also be too slow, and I figured symbol comparisons would be faster. Turns out, no, they're not, converting the strings to symbols and comparing them is a little bit slower than just comparing the strings to begin with.

My other optimization idea, to take a line of the form (angle / 10).to_i and change it to (angle.to_i / 10), did speed things up a bit, due to the fortunate elimination of floating point division.

At this point the game is smooth again in sectors with lots of ships, but not when they start shooting the hell out of someone. There are a few other spots in the code I can optimize, fortunately. I would like to avoid having to drop down to C, as I know that this more likely indicates a failure to optimize on my part than it does a shortcoming on Ruby's part.

Day 158: Reno

I'm currently attending the Smalltalk Solutions conference in Reno, where we sent a man the #kill message, just to see if he understood.

Trust me, if you know Smalltalk, that's a very funny joke.

Good news/bad news on the development front. The good news is that I finished the two tickets I mentioned: #179 was for making lasers that weren't slower than the ships that fired them, and #180 was all about adding "scenery" ships to the tutorial.

That's where I ran into the bad news. The first problem was that adding weapons to the ship in the editor still wasn't working, despite all the testing I'd done. What happened on the back end is that any time you give an object a tag, if it had a tag before it deletes it from the repository and re-registers with the new tag. Whenever I created a new weapon for a ship, it replaced the original! So if I had 2 lasers on board, suddenly the object in the repository would be those same two lasers. It was a mess to track down, but thankfully a relatively easy fix.

The worse news was that it's slow! There are noticeable hiccups in even sectors where there's no combat. The traditional profiling approach of using 'ruby -r profile' had so much overhead I couldn't actually run the game. Thankfully, Ruby-prof exists. It's got a small enough overhead that I can actually play my game.

I have a lot of data to go over for optimizing, and I'm not releasing the tutorial until I'm done, so I have one more ticket to close, #96, which I have aptly titled "OH GOD MY CPU".

For those interested in my contest entry, I am temporarily hosting it on my own machine, so get it while it's still up! I think the contest turned out really well and I'm happy that my entry ended up as good as it was.

Friday, June 13, 2008

Day 153: Contest

I'm taking today, tomorrow, and Sunday off to participate in the RubyWeekend game creation contest. This is the first extended break I've taken from the game since I moved into my new house, and I need it!

I did get some stuff done yesterday, mostly tweaks. Specifically, fixing the combat tutorial and making tutorial items cost money.

This puts me two tickets away from finishing the milestone and therefore the tutorial. I'll probably do a few things at that point:

  1. Create a github project for the game and upload it there.

  2. Branch the current version off and name it kuiper_rubygame_3.0.0. This will be a reference branch, in case I need to port everything back to Rubygame 3. Why make this branch? Because of the next step:

  3. Backport the game to Rubygame 2.3. I've had people interested in at least downloading and playing with the game, and as I'm using the bleeding edge my-version-may-break-daily version of Rubygame, it discourages people from doing so. I'll have the Rubygame 3 branch ready when I need to switch over. Finally, I'll:

  4. Release a 'preview'. This will consist of the tutorial and be announced here and maybe a few other places (forums only, no formal announcements yet). Get feedback, etc.


And after that, I start work on the main campaign!

Wednesday, June 11, 2008

Day 151: It's Never Easy

Today's subject is a ripoff of a line from the Phantasm series of movies. The movie's been around for as long as I've been alive, so I'm about to spoil the ending.

When the intrepid heroes finally kill the bad guy, they sit back and have a moment to recuperate. They do all their "Sure is better now that the bad guy's dead!" talk, and one of them says "Thank God, it's over"

That's when the bad guy appears behind them, stating "It's Never Over", and the movie ends. All the sequels followed this pattern. This was the first horror movie I saw that that didn't have a "protagonists escape and everything's better" ending (I hadn't yet seen A Nightmare on Elm Street)

So just when I think I've got an easy task ahead of me, I open the editor and the Tall Man appears, looming, intoning "It's Never Easy!" and then throwing killer spheres with knives into my head. Trust me, this is very scary if you've seen the movie. Especially if you saw it at the age of six.

The easy task that actually was easy today was #99, better radar coloration. Since everyone can get ahold of the player's ship, it was trivial to change the color if the current ship was either the target of the player or targeting the player.

I had plenty of time left over after that, though, so I tried my hand at #184, having a 'nearest hostile target' button. Writing it was not hard, and as it turns out the logic was mostly right. The problem came in testing. I'd start up the game, shoot someone until they were angry at me, and hit the button.

The game would freeze.

What this usually means is that it's about to crash and Ruby is about to give me a stacktrace. I don't know if it's Rubygame or my machine or what, but sometimes this takes anywhere from 5 seconds to several minutes to actually happen, and it's eating up 100% of the CPU the entire time. Very inconvenient for debugging, and incredibly inconvenient for detecting infinite loops.

I spent an hour. I thought the error was in the comparison code, as that's one place that it'd be easy to get stuck in a loop. I had print statements everywhere. It was yet another horror of coding, and nothing I did helped.

Until I put a print statement at the very end of the targeting code, that is. This entire time, I'd been assuming it was this new code locking up the game. But it wasn't! Debugging told me that the targeting logic was all working. Furthermore, looking at the output showed me exactly what I was doing wrong. I was doing this:

player_ship.target = chosen if chosen

But 'chosen' in this case is a sprite, not a ship. The player_ship is expecting a model object, not a view! All that work, for a six-byte change:

player_ship.target = chosen.model if chosen

Thus, today's Programming Tip of the Day: Don't try to use your view as your model.

Tuesday, June 10, 2008

Day 150: On Automatic

Today I set out to do #178, the in-game quicksave and autosave. Because I already had a utility function that could save any time from anywhere, I figured this'd be a cinch. Create a new control and if it's pressed, save. Turns out, it really was that easy....

My wife came in at this point and asked me what I was doing, so I mentioned that I was working on autosave. She said:

"Oh good, but make it so that it doesn't overwrite the main save file, I hate it when games do that!"

Guess how my save function works! It can't save using any filename other than the universe's name, which happens to be the main save file.

The bulk of that work, then, was fixing that problem. The issue is that if I just tack '-autosave' on to the end of a universe's name and save it, that name persists. So the player, upon loading their autosave and autosaving /that/, gets a name like 'scenario-autosave-autosave.ksg' That would not do.

The save code now looks for two identifiers ('autosave' and 'quicksave'), strips them out, and then adds them back on if you are, in fact, quicksaving or autosaving. I also added in code to put the player's name into the filename as well, so more than one person can play per user account and not have their saves clobber each other. Now if only I could name the player.

I had some extra time, so I got #177 done. That was literally a two-line change, to make some of the editing field bigger. I had time left over.

So, why not, I figured I'd tackle another "easy" task: #160, backporting all the nifty changes to multi-line inputs to the old one-line input.

The plan was, make the one line input a subclass of the multiline, and disallow the creation of new lines. This was easy, but the line was still multi-enabled; it'd wrap if you put too much text in it.

Disabling wrapping was difficult - the MultiLineInput itself delegates the work to the MultiLineLabel. I ended up having to have my subclass override its parent's rendering code entirely (as that's where the wrapping happened), and then set a number of variables that the parent class expected that the rendering would set. It was a pain.

Oh well. At least no global variables have bitten me in the ass.

Monday, June 9, 2008

Day 149: Dep^H^H^HBreadth First Search

It'd be nice if I could implement a Breadth-first search without first accidentally creating a Depth-first search like twelve freaking times.

If the last entry was a blast from the past, this one's an archaeological find. I closed #4, which implements the "Plot Course" button. I did this because the multi-jump tutorial mentions it, and I want everything I mention in said tutorial to be working before I move to the next milestone. This is my new way of putting things off; mentioning them in the tutorial and going "Well, gotta implement this!"

Only this time, I wasn't putting anything off. I've finished #157, and #158, which were the last two editor-intensive parts of the tutorial. So I'm perfectly justified in coding up whatever I feel like.

This blog entry is backwards, because before I did either of those things, I did #176, which lets you display a message to the user once and only once (so they're not constantly pestered with tutorial reminders).

Before I did that, I fixed a bug whereby weapons weren't taking up the right amount of space (their expansion and hardpoints cost was only counted once, regardless of the number of weapons you had). Once again, I discovered that unit testing will only help me if I actually write the tests; I asked myself "Didn't I test for this?" when the bug came up, and the answer turned out to be a big fat "NO!"

And before I did that, I proliferated MiniBuilders to weapons and ammo, which wasn't using them before. At this point I tend to take my editor's stability for granted, so it's very disconcerting when I click a button that's still there because I haven't put a minibuilder in and everything dies.

Finally, the global variable has been quiescent, not biting anything. But I'm watching it....

Friday, June 6, 2008

Day 146: Blast from the Past

Yesterday I finished #168, which prevents the user from seeing (in detail) sectors they haven't been to yet, only to discover that when you add them to your itinerary you still see the names (#175).

Today's subject comes from the fact that, as of earlier today, I had 42 outstanding tickets in milestone:Polish. This is the milestone I throw things in which aren't immediately necessary, but that I want to get done before I ship the game. It's got more tickets in it than any other milestone, because anytime I feel like putting something off, there it goes!

So today I finished #76, the ability to do a quicksave of a universe while you're editing it. This is invaluable because the alternative is to go all the way back to the first editor screen, save, and then immediately load up your scenario again to continue editing.

I had to make two changes in order for this to happen. First, I had to make it possible to respond to a keypress at the engine level; because the editor can be in any one of a dozen states at any time, the engine is the only place they have in common. I was already doing this with the 'kill switch' that shuts the game down (good for testing full-screen!), so I extended it to call arbitrary code in response to any button.

The second change was surprisingly easy. Games-in-progress and scenarios-being-edited, while sharing the same file format, are saved in entirely different ways. I have plans (#169) to widen that gulf even further. So if I'm going to be allowing the person running Kuiper to save the universe they're editing at any time, there needs to be a way to know that the person is, in fact, editing a universe and not playing a game.

Kuiper uses a number of gameplay displays as editors; the map and sector states are examples. Each changes its behavior if it's in "edit mode". A number of other things change if the user is editing. So each thing which cares about edit mode has its edit_mode set to true (usually in its initializer). The problem is, half of the project cares if it's in edit mode! TraderMissions did something similar, but worse; the repository itself was given to everything that needed it, which turned out to be everything. I've done it enough and had it cause enough code bloat to dub it my own personal Anti-pattern: Constantly passing objects that should be global.

So I committed one of the cardinal sins of programming: I made a global variable. It's one of those things you're supposed to do only if you know what you're doing, and usually that rules me out. This time, though, it's making life a lot easier. And that is the story of #174.

The other thing of note I did today was create the KuiSellItemsAction. No, it doesn't force the player to sell items, like I keep thinking when I see it, it puts the items you indicate up for sale on the planets you tell it to. You can sell weapons, cargo, ships, and/or addons. This is useful for, say, simulating technology advances in-game. I use it in the tutorial to only offer weapons to the player after they get the weapons tutorial.

Now taking bets on how long it'll take the global variable to bite me in the ass!

Wednesday, June 4, 2008

Day 144: Your Ship has Come In

So I decided on a whim to cater to that tiny but vocal minority of players who might one day want a ship other than the horrible tiny one they start with. #170 is thus done!

In the fine tradition of making things worse for myself, I then made #171, #172, and #173.

#171 should prevent people from jettisoning mission-related cargo, which is a surprisingly hard fix. The part which displays this is too general to know that it's even displaying cargo, let alone which is mission related. I plan to cheat and have it check to see if its object understands :mission_related, and disallow jettisoning. This also opens things up to one day having missions give the player temporary weaponry and/or addons.

#173 is my pie-in-the-sky ship previewer. When comparing ships, it'd be great to get a side-by-side comparison, complete with how many more (or less) of a given attribute the new ship has. That's easily extended to addons, too.

#172 is, um... making tutorial items actually cost money.

One of my bosses at work has a Staples easy button that says "That was easy!" when pressed.

I'm lobbying to have the voice clip replaced with "That was surprisingly difficult!"

Tuesday, June 3, 2008

Day 143: Addons

First, the huge news: #149 is done!

Okay, that's not huge news. As alluded to before, I've been planning to remove ERB for a while. Today, I wanted to blow another 15 minutes. Thus, an easy ticket (which turned out to actually be easy) was closed.

In more important development related news, #156 was also closed today. That's the Addon tutorial. It took surprisingly long because, as I was testing the tutorial module I asked myself "Self, what if I set out to specifically disregard the tutorial's instructions."

It breaks things, is what!

I had to create a new condition that would check if you'd actually finished a mission, just for this. Now all the tutorial missions are only awarded if you've finished their prerequisite mission. I expect that condition to be handy for multi-mission story arcs, and now you don't have to use flags (which are a pain) to do the same thing.

Finally, I made ticket #170 because it occurred to me that maybe - perhaps - a small minority of players could, at some point in their gameplay experience, want to buy a new ship.

Yeah. I forgot about that until now.

Monday, June 2, 2008

Day 142: Two Hundred Hours

Actually, because I was only a half hour short yesterday, I'm at 201 hours. But still, it's a milestone so I'm naming my blog after it.

Today's work was finishing #166 which means the market dialog is shiny and good and happy. You get details, # of everything it requires that it requires, # of that thing you have free, and the ability to buy/sell more than one of them at a time. I even made it work with weapons, even though that ticket's not up yet.

That's not to say things went extremely well: I had to quash a lot of typos. 15 minutes into some refactoring I decided I didn't like the way it was going, so I ended up reverting all my work up until that point. I kept putting Python syntax in Ruby, thus leading to today's Programming Tip of the Day: To make a new object, use "new".

Still, many things went right. For instance, everything that can be bought and sold has its price stored stored in an instance variable named 'price'. Thank you, me, for making that consistent! Getting weapons to work was literally four lines of code, all of which were copied and pasted from the addons adapter. I got an extra half hour of work in.

Finally, now that the market dialog is handling everything market-related, and the minibuilder and new builder dialog is handling everything build-related, I deleted the old builders. Three entire files (addons.rb, cargo.rb, and weapons.rb) housed nearly identical GUI code that is now handled in one place.

Of course, the downside of this is that, according to my spreadsheet, I wrote -190 lines of code today. I'm still calling it a win.

Sunday, June 1, 2008

Day 141: Slow Ride

No, that's not the title because I took it easy today; it's because my lines of code increased by a whopping 30 over 2.25 hours of work.

Of course, LoC don't tell the whole story - I'm surprised it was positive, as I did some refactoring today and removed a fair bit of code (the old market dialog, for instance). Sometimes the most productive days are the ones where the LoC count goes backwards. That's one of the other ways this differs from Nano.

Today was touchup. #165 required me to put scrollers around the new fancy editors I'd made, and I did it and it was surprisingly easy.

#166, on the other hand, was not so easy. It requires me to have a 'preview' of the cargo/addon/weapon you want to buy. Not a big deal, the preview part was easy. It was the "This requires 1 hardpoint and 2 expansion space, and you have 0 hardpoints and 0 expansions left" part. I essentially made it so any Buyable adapter knows what kind of requirements the things it holds have. The ShipCargoAdapter has "cargo", ShipAddonAdapter will have "expansion" and "hardpoints", etc. These are mapped to actual function names, so I can then dynamically generate the message.

That part's done for the "You have 3 cargo free" part, but not for the requirements. I'm already over my quota for the day, though, so on to tomorrow!

Procrastination for the win!

Saturday, May 31, 2008

Day 140: Map and Jumping

I finished #155 today, and named my blog post after it. The total explanation for how to use the map and how to jump out of the sector was maybe two paragraphs, and it took the entire two hours to do. Thankfully, it's not because I'm a slow typist.

No, the reason it was so problematic was #162. The previous tutorial mission which tells you to land on the planet only ends when you get the map-and-jumping mission. In order to do this, map-and-jumping sets a flag upon receipt, and land-on-planet checks this flag to see if it's ending. If it is, it removes itself.

I have a condition check for this. It had three fields: is_set, number_is and note_is. Those last two are to check if the flag is set to the given number or has the given note attached. Is_set was, I thought, to merely check to see if the flag was set.

I checked the unit tests to find that its purpose was originally the exact opposite. If you didn't specify is_set, it would return true if the flag was not set. Otherwise, the note or number had to match.

So there was some refactoring. Now is_set does what I originally wanted, and there's a new is_not_set which, if selected, returns true if the flag hasn't been set yet.

That was working, the land-on-planet mission was vanishing at the right time, but the map-and-jumping mission wasn't working at all: it was supposed to pop up an informational box about how to, well, pull up the map and jump the ship around. It wasn't. I started having flashbacks to my two-day multi-hour coding binge of doom. I really didn't want there to be something I'd have to re-code the entire notification system for.

And, lucky me, there wasn't. In fact, it was the other mission, the land-on-the-planet mission, that was causing the problem. The loop which checks all the missions looks like this:

@player.missions.each do |m|
m.check
end

The problem being that check can end the mission, and thus remove it from the @player.missions array! This messes up the loop, and it ends up thinking it's done and not checking the other mission.

Thus, @player.missions.dup Life was surprisingly easy after that!

Friday, May 30, 2008

Day 139: Miscalculation

"Hey Roger, when did you start this thing, anyway?"
Yesterday, you might have thought I started it sometime in early February, which would have made it day 119 and been a rather belated New Year's Resolution. I actually started keeping track of everything in a spreadsheet on January 12th. I just today discovered a mistake on my spreadsheet that wasn't counting 20 days of work. So the answer to your question is no, watching the Lost season finale did not catapult you 20 days into the future.

Today I got #120 and #131 done, which means the new market dialog is live! I didn't take any 'before' screenshots, so imagine something that's worse than this 'after' screenshot:



Also, those numbers in the 'status' bit haven't changed, but they're important because I made a huge change to the way cargo was handled. All the tests still pass, though. Ship it!

Wait, no, don't ship it yet. I was kidding! Put down the packing tape!

Thursday, May 29, 2008

Day 119: Lost

My excuse for today's entry being short is that I'm typing it during the LOST season finale. I recall, when I first started this blog up, I feared that all my entries would be short. A friend of mine reassured me: "This isn't NaNoWriMo. You don't have to pad your word count." She makes an excellent point!

Today I finished #154, which is the tutorial telling people what you can do on a planet. It goes into some detail on trading, which is special because that doesn't actually work yet. This brings me nicely to my next topic: I didn't so much get things done today as create a whole slew of things to do:

Moved #120 and #131 to this milestone, because I'll need them in order for the tutorial to be accurate. Created #162, #163, #164, #165 as cleanup tickets to make things look better and more functional.

In short, my total number of tickets for this milestone went from 7 to 11, and my completed number only increased by one :)

Wednesday, May 28, 2008

Day 118: Medicated, again

If you glance back at the blog there, you'll see the last time I was medicated was at the very beginning of the month. Believe it or not, this is a huge improvement.

Regardless, this will still be a short entry.

#125 is done. You can scroll to your heart's content, and all will be well.

Before:


After:

Tuesday, May 27, 2008

Day 117: Caret

Short blog today, as I'm still not feeling so hot. And I ought to, considering that it's 90 freaking degrees outside.

Today I got 'home' and 'end' to go to the beginning and end of line. I got the editor to correctly recognize more than one return character (this was also causing off-by-one errors when it wasn't working). It turns out that String::split will ignore trailing whitespace unless you tell it not to. Yes, I know the docs I just linked to say that, but I hadn't considered looking there until things weren't going right.

Programming Tip of the Day: Read the Fine Manual.

Other work includes interpreting mouse clicks and positioning the cursor appropriately. After that I did arrow up and arrow down; the clicking was needed because I simulate moving the caret up by pretending there's a click on the line above the current one. This works remarkably well.

Back to sleep!

Monday, May 26, 2008

Day 116: Line10k

I finished my compulsory two hours of work today two lines of code short of 10,000, so I went on to work for another two hours. If you look at the summary to the right, you'll note that in that two hours, I wrote a whopping 10 lines of code. This was because there were tons of bugs and it required a lot of subtle fixes that were also wrong.

All four hours were spent on #125, the ticket to put in saner text editors. As it was before, you could only change the last bit of anything you were typing. So if you'd made a typo a paragraph earlier and just now noticed it, you'd have to delete your way to it, fix it, and then re-type everything.

Today marks the second time I've had to sit down and do some work on paper. It initially seemed easy to figure out where a cursor should be drawn; I was iterating through each line of the input, adding the size of that line to a running total, which I would then compare to the caret's position. This was error prone and hard to tune.

Then, after writing down an example, I figured out that I could determine which row of the input the caret was on by counting the number of newlines before it.

Later, I kept getting these strange off-by-one errors. I'd been thinking that the caret - an index pointing to where in the original string any changes will be made - was off from the displayed string because the display had extra newlines put in there from word wrapping. It wasn't until I wrote down a sample situation that I realized the extra newlines were replacing spaces (because that's where the words would wrap) and my intricate correction code was causing the off-by-one problems.

Additionally, because I want 'home' and 'end' to be valid text editing keys, I had to change the "emergency end the program" key away from 'end'. It's the backslash now. I accidentally ended the program twice today anyway.

For those interested, the 10,000th line of code is:

<child name='flags'/>

Sunday, May 25, 2008

Day 115: Tutorial

Programming work was done yesterday, but by the time I finished it was no longer day 114. 1.75 hours of programming was enough to finish up the Missions milestone! From now on, it's module development.

The problem with tracking my progress is that every file saved by Kuiper was just one line. One gigantic line with no linebreaks - it's XML, so this actually doesn't matter, but it makes reading it with an ordinary text editor a pain. It's this way because any whitespace was interpreted as a text node, and my code wouldn't check for that, instead trying to re-create a ship from whitespace. This is even more impossible than it sounds :)

So the first part of my day was spent changing the way the XML serialization works. It now skips over any text nodes (I don't use them; all text ends up in attributes, which is a bit messy but works) and once more passes the tests. With that out of the way, I can now use Cloc to count work on my module as lines of code.

The first module I'm making is, in my opinion, one of the most important: The Tutorial. One of the reasons I'm doing this now as opposed to later when the editor is more stable is that I'm trying to shake bugs out of the editor. Later one of my goals is to make a scenario-making tutorial, and I'll likely rewrite the original tutorial then, so if I mess up now it's not a huge deal.

Two bugs fixed today in support of the tutorial:

#159 If you ever lose sight of the planet, it's very easy to get lost and not find your way back. Even for me, and I wrote the darn game! Now, if the center of the sector starts leaving sight, a green arrow appears on the radar to show you which way to head back.

#132 Is the first part of the tutorial, giving basic info on how to move around and land on planets. This one helped to reveal not exactly bugs, but things that would be nice. For example, I've had #125: Better Text Editors open forever, but I've moved it to the tutorial milestone because now I'm typing a lot of text and it'd be nice to be able to edit it in a sane manner.

My wrist hurts from typing; I'm taking the rest of the night off!

Friday, May 23, 2008

Day 113: MVC...E?

Everyone knows about Model-View-Controller, but given that I've built my editor into my game, I feel like I've added a fourth leg: Editor. Now, my editor's pretty smart about inferring what it needs to display from the model, but there are things it can't handle, and that stuff I need to update.

Eventually, I may be able to change that. The Kuiper module format (and save game format) is XML, and I'd like to be able to export an XSD - but to do that, the models are going to have to have more information about the types they understand. With that information, I can make my editor smarter, and return to the MVC trifecta.

#146 is done, and was easy given that I got all the unit tests yesterday. I had to mirror the refactoring I'd done to its model in the editor, and that was about it.

#147 is also done: From a model standpoint, random fetch quests look exactly like random fedex quests. I was halfway through writing the editor when I realized this and scrapped it, using the fedex one for both. I thought I had a bug where I was getting more than one cargo for one mission, but it's possible I simply accidentally picked up more than one mission. I've been unable to replicate it, regardless.

Finally, because doing so makes me within 1 bug of finishing milestone:Missions, I did #142. The halo system was working fine, but ever since I did the de-spawning action (which takes a spawning action to undo), I wanted to backport it to halos.

I have a tradition where, if I finish a milestone, I can stop work early for that day. But I kinda want to catch up on lost time (my spreadsheet informs me that I'm still 8.5 hours behind schedule) so I may actually keep going.

We'll see....

Thursday, May 22, 2008

Day 112: Branching Out

Good news, everyone! Rubygame 2.3.0 is out!

Actually, as it turns out, that's not good news - at least not for me. 2.3.0 is an interim release between the old Rubygame and Rubygame 3000 (or, as it is more properly known, Rubygame 3). When Rubygame 3 development began, I ported my game over to it. The release of 2.3.0 isn't a big deal so far as that's concerned, because all I really need to do is make sure that my game is released sometime after Rubygame 3 is, and my game's going to take a while to be released so this isn't a problem. No, the reason it's an issue is because...

... I'd have to learn about branches.

Rubygame trunk switched from 3.0 development to 2.3, and 3.0 was moved into a branch, and I had no freaking clue what that meant. Thus, I had to do a little research into Subversion. It turns out to be pretty easy. I went to my rubygame working copy and did a:

svn update

Then, following the directions as laid forth in this section, I did:

svn switch
https://rubygame.svn.sourceforge.net
/svnroot/rubygame/branches/3.0.0

(Whitespace added so the edges aren't cut off - that's all actually one line)
That command gave me the following error:

svn: Failed to add directory
'ext/chipmunk': object of the
same name already exists

The obvious solution was to blow away the chipmunk directory and update again, which seems to fix it.

Disclaimer: I don't use any of the chipmunk features in my game, it having been begun before the library was integrated into Rubygame. So you may have to rebuild your chipmunk.

In the usual style of my posts, all the above text took 15 minutes. I spent the other 45 refactoring code out of the random cargo generator and into #146: Random scout missions. It was pretty easy to write those, as the cargo generator already had code to generate random destinations, and that's all scouting missions are.

It passes its tests, but doesn't have an editing component. That's tomorrow's work!

Wednesday, May 21, 2008

Day 111: Working for a Living

I pulled a 12-hour work day today, and still managed to code something up. I am super-programmer!

#148 was closed today. I meant to do it yesterday but forgot - given that you can do pretty much everything random mission related at this point, it's pretty much done.

#150 was a bit more work. The bug where the ship claimed to have twice as much cargo space as it ought to was due to the fact that it claimed to have twice as much everything! I had been wondering why my jumps between sectors were taking 6 seconds instead of 3. This I found was a math error on my part, which I fixed.

I was repeating myself in the ship cargo code - there were separate arrays for mission and non-mission related items. This was stupid. Now there is one, and the ship is smart enough not to lump mission cargo and non-mission cargo into one entity even if they are otherwise similar.

My game file format is pretty backwards compatible. If I add an extra field on in a newer version and then load up a scenario made in an older version, whatever extra field I added just gets its default value (which instantly makes my homebrew XML-based solution preferable over YAML, which sets it to nil). It had a problem doing the opposite, however: if I removed a field and loaded a file which still had it, it would complain loudly and then die. Now it complains loudly in a warning message duly logged, which you can then change the log level of if you're tired of hearing it.

Super-programmer, away!

Tuesday, May 20, 2008

Day 110: #150!

Today I filed my 150th bug. #150 exists because I did something I hardly ever do: I stopped working on fixing something because I felt like I was done for the day.

Most of the work I did today was on #148, specifically letting the player edit the mission generator and then having the planet pick it up. These were both time consuming, though at least it was for different reasons.

Editing a random mission generator uses the same setup as editing conditions or actions - because there are many different kinds, the user sees a list on the left of all subclasses, and clicking one gives a more detailed editor on the right. Until today, I'd been committing sins against object-oriented programming: The part which rendered editors for the different subclasses was one big case statement.

I atoned for that sin today, removing the old code altogether and creating a hierarchy of handlers that know which subclasses they understand and render them accordingly. Much refactoring was done!

Actually using the editor turned out to be problematic. Every condition and action has a 'post_load' method which sets up some instance variables. For the randomly generated conditions and actions, this was never called because they're generated at runtime. They never load! This wasn't caught by the unit tests because they explicitly post_load everything. I've fixed all of this, and conditions/actions no longer rely on post_load working.

So back to #150. Everything appears to work! You can go to a planet, land, pick a number of random missions, accomplish them and get paid. Only problem is, you can pick as many of those missions as you want. I can't seem to get randomly generated cargo to count against your maximum amount. Also, that max seems to be reported as '20' instead of '10'. Strangeness abounds...

... but I'm sleepy. It'll wait!

Monday, May 19, 2008

Day 109: %BLOGTITLE%

For some reason I didn't make a ticket for this, despite knowing it was what I'd have to do next. Ah well.

Today's blog title represents what I did today. As I suggested I might do in yesterday's blog, I worked on templating. So now you supply the random mission generator with a template mission. The resulting missions have a name with %TOKENS% replaced with sane strings (via lookup table), and also have whatever conditions,actions,and checks the template mission had in addition to the ones required for them to work. There's also a 'cleanup' child on the generator which will be appended to the end of every 'end mission' action sequence, so you can undo what you had your template setup do.

"Wait a second," you say, "Why do you go through all the trouble for %TOKENS% replacement? Can't Ruby do this for you?"

Why yes, yes it can. Embedded Ruby (ERB) is heavily used by things such as Ruby on Rails, and manages to interpret ruby in a safe way. This is one of my big concerns; I don't want someone to distribute a Kuiper adventure that wipes your home directory if you fail a mission!

The problem with ERB is exactly that it was made for webpages. Take, for example, this bit of sample code:
Take 5 tons of Weapons to 
<%= @mission.destination.name %>

Now, in the context of a webpage, ERB works great. You, the web designer, provided that string above, so you know it doesn't contain any nasty code. The part that might be bad is @mission.destination.name, but ERB knows to evaluate that in Ruby's safe mode.

In the context of a game however, it fails utterly. Sure, you - the scenario creator - provided the string, so you know it doesn't contain nasty code... but the user doesn't. The roles have been reversed! It's the user who has to run a string that might be unsafe. ERB fails in this instance because the first thing it does is call ruby's eval... on the tainted string.

Thus, I have created a ticket: #149 compels me to remove all ERB and replace it with my less capable, but at least safer, alternative.

Sunday, May 18, 2008

Day 108: Random

I think I may stop labeling posts with the bug numbers mentioned in them, mainly because it's generating a ton of labels. Of course, if I stick with that it means I'll have to go through all my old posts and strip them out. My main reasoning is that, so far, I've never had to go back and look at a bug I fixed, then look it up on the blog.

Today I got back into the swing of coding by fixing some bugs: #143 and #144. So now when you put a planet in a sector it won't vanish mysteriously, and you won't be prompted to take on a mission while you're editing a sector.

That was just the appetizer, though. The main event was #111: Random missions. Well, there's now support in the engine for randomly creating fedex quests, though there's none in the editor or in playing the game. Most of my time, in fact, was spent writing a giant unit test which only took the most basic scenario into account. Still, it's all working and apparently working well, so tomorrow I'll probably move on to putting it into the game

To give me a bit of inspiration, I looked back at how TraderMissions had done random missions, and the answer was "Horribly". There was no difference between random and regular missions, random missions just had a few more children populated (i.e. a 'random cargo' child, which was empty in the case of a non-random mission).

My current approach separates regular missions and random missions entirely. Random missions don't even inherit from regular missions, instead they create them. If I were to have discovered this on my own, I'd have named it the Factory pattern.

There's still some issues, though. The generated missions need details like names, descriptions, etc. There's enough in common between regular missions and random mission generators that I'd like to avoid repeating myself. I'm thinking of having a 'template' child for the generator; this child will be a mission - the name of it will get expanded into the names for all of the missions. So the template mission name would be something like "[%ORGANIZATION%] Take %CARGO% to %DESTINATION%" and the resulting generated mission name would be "[Rebels] Take Fruit to Mars"

It's good to be back!

Friday, May 16, 2008

Day 106: That Bounty is Mine!

No blog yesterday because I was incredibly busy at work, and only managed a half hour of my project. I'm having company visit over the weekend, so expect a lack of entries then, too.

I did accomplish something, however! #141 is working well.

My first plan for tracking when you killed your bounty was simply to have the sector keep a list of every fleet you'd ever killed. This is less stupid than it sounds - it would only record the base of the fleet (for example, 'fleet_earth_patrol') rather than the specific instance of the fleet you'd killed. Since the base fleets already exist in the game, it's not taking up that much more space.

This wouldn't have worked, however, because you'd have to have a separate fleet for each and every bounty mission you ever wrote. Considering that I want random bounty generation to be possible, this wasn't the way to go.

So now the KuiSpawnAction which spawns the ships has a special flag called "watch_for_destruction". Any mission that performs an action with this flag adds the fleets that spawner made to its list of fleets to watch. Any time the fleet is destroyed, the mission is notified. There is now a KuiDestroyedCondition which looks at the fleets that have been wiped out, compares it to the watched fleets, and returns true if it passes.

That worked fine, it turns out; except that the instant you kill the fleet, it spawns again (because in my test scenario, it has a 100% chance to spawn) even though you've satisfied the conditions to end the mission. This is because the condition won't be checked until you land on a planet or leave the sector. Even if I made the conditions be checked immediately, that still wouldn't solve the case where you have two fleets to destroy, one in this sector and one somewhere else.

I spliced in a bit of the original idea: When you kill a fleet, if it's watched by any of your missions, the sector remembers that you killed it. Then, when it comes time to spawn the fleet again, it looks at that list and doesn't do so if the fleet is unique (which almost all of the spawned ones will be). De-spawner actions remove fleets from the list so it doesn't get gigantic.

Finally, 'end misson' actions now clear out the list of fleets to watch and fleets that have been killed for their mission. With that in place, the mission is now repeatable!

Wednesday, May 14, 2008

Day 104: Not Everybody Hates Roger

Long ago, before I even started this blog, I was testing the game over screen. There were one of two ways for me to do this: I could either put in a 'self destruct' key that would end the game and then accidentally yet constantly press it while doing other things from that day forward, or create a number of badass ships with tons of weaponry that hated my guts. I opted for the latter.

And only just today, months after the fact, did I bother to change the faction back to being neutral.

What can I say, it made things more exciting!

The reason for the change back was to test the results of #140, my first stab at bounty hunting. I spun off the "verify that the target is dead" part into another ticket, so thus far the only thing you can do is to put fleets into a sector and remove them.

Initially, I was going to do this by having a special field in the sector for 'mission fleets' which would always spawn. TraderMissions did this similarly, but more stupidly, by spawning the fleet if you're in the sector when it checks on the mission (much how popups are done).

Then I thought perhaps the editor wouldn't want them to always spawn, but only sometimes. I already have a handler for that, and it's the same "potential fleets" field that the sectors already use to show fleets. So the new actions just use that. I created a 'unique' flag so if you want to spawn pirates that are guaranteed to appear and never go away, other identical pirate groups don't also spawn in.

This turns out to be more powerful than the original approach would have been. Now a mission to deliver, say, planetary defenses could eliminate a (not otherwise mission related) enemy spawn from that sector.

The de-spawning action does something new - one of its children can be a spawner action, which the de-spawner then proceeds to undo. This is because I forsee 'Bounty Hunt' style missions be of the form "Go here, kill this fleet", and you don't want the fleet to still be there once the mission is over. It's a lot easier to just say "Undo this spawn action" than it is to remember which fleets you spawned and which locations you spawned them in. I'll probably do something similar for the sector highlights.

In all this, I had to create a new fleet, which is how I discovered that over the weekend I'd completely broken the "old way" of building things. So every time I run into this, I'm installing a MiniBuilder, which seems to be the way to go.

This has been another productive day. I seem to be alternating.

Tuesday, May 13, 2008

Day 103: Blogging - "Kuiper Dev Blog"

Today's subject likens to that two-line fix I did today, #35. That's right, when editing a module the game will change its window title to "Kuiper - Editing 'Your Game Title Here'", and when playing it'll be "Kuiper: Your Game Title Here". This ticket was put in a long time ago because I couldn't tell my editing and testing windows apart. I had some spare time at the end of the day and hacked it in.

Sadly, that was the only easy part of today. Most of the work was on #135, getting sectors to do what Planets were doing easily - in this particular case, that's handling missions. The main problem was that planets' checking was simple, they had to do it once because a new planet GUI is re-created every time you land. The sector GUI sticks around when you go to other states.

The 'return from another state' code looks like this:

def awaken
@continue.call if continue

do_mission_checks
end

It has to be like that, because do_mission_checks requires the continuation support in order to work. But the problem is, if any other state sets @continue (and they all do), then it's called and the mission checking never happens.

The fix is, take every state you're transitioning to with a continuation and, when it comes back, force a call to awaken:

def land
callcc do |continue|
@continue = continue
@driver << planet
end
# We'll resume here
simulate_passage_of_time
awaken unless @continue
end

That way we call awaken when we need it to process mission events, but not if calling it would call a continuation. All told I wrote about 8 lines of code, net. If only all days were as simple as yesterday!

Monday, May 12, 2008

Day 102: Adapting

Today was a pretty standard day, code-wise. I worked on #136, which displays all the missions the player has in case they need to be reviewed at any point. TraderMissions had dialogs for cargo, missions, weapons on board, etc, and they each had a different key you'd have to press to get to them; this time I was determined not to be so annoying. Thus I'd long ago made #62, a request for a show-everything-about-this-ship dialog.

Well, if you've been reading this blog you know by now that I just can't leave well enough alone. Rather than start immediately on the mission specific dialog, I decided to make a general dialog.

I discovered pretty quickly that I'd been thinking about this all along - the cargo dialog inherited from a ShipPartViewer that I'd obviously intended to be the parent of any such states. So if I wanted to view addons on the ship I could subclass it to create an AddonViewer, etc. (Indeed, I discovered, I'd actually created those viewers if not hooked them up to events)

Creating a subclass for every type I want viewed seemed... familiar somehow. It's exactly the sort of thing I created the adapters for, so rather than go through the comparatively simple process of writing a MissionViewer subclass of ShipPartViewer, I took the more difficult path of writing a PlayerMissionAdapter, then refactoring ShipPartViewer to use whatever adapter it was given for display purposes. That done, I could remove all the other ShipPartViewer subclasses entirely!

This sounds much more complex than it was, mainly because ShipPartViewer was already made to be extendable, so the retrofit was not very difficult. Unlike the weekend, I did not spend hours tracking down subtle continuation bugs. I just coded up a few adapters, fixed some typos when it broke, and was finished for the night.

I need more coding days like this one!

Sunday, May 11, 2008

Day 101: Time Travel

FINALLY, I got #137 done. It feels like I've been working on it forever, probably because of all the work I had to do yesterday.

The fun thing is, today I ripped out most of that work and replaced it with an entirely different way of working. One that was more unit-testable, for one, which made it a lot easier, and what I ended up doing made a lot more sense.

Let's say you were going to land on a planet that had a plot mission for you. Here's how it would work before:

  • For every plot mission, find out if it's awardable:


    • Return false immediately if there's any condition we can determine right now is failing

    • Otherwise, return a list of each condition that's going to need user input.


  • If we got a list instead of 'true', go through that list and pop up questions for all of it. If they're all answered right, then go on to award the mission.


    • If any of these actions is going to need to display something on the screen, return them right now before awards are done.

    • Otherwise, do the actions


  • Again, if we got a list instead of 'true', we have to go through the list and make sure the user sees all of the dialog. Then call the award function again but tell it to ignore calls to pop stuff up.


This was incredibly complicated and error-prone, as demonstrated yesterday. I kept thinking of how handy it'd be to be able to, when we see a condition or action that requires user input, stop right there and do it, then come back. If only there was a mechanism that let me do that which I'd mentioned before!

So, continuations to the rescue! The hardest thing about reimplementing the system to use continuations is that my analogy from the other day was more apt than I knew. Continuations are a time machine. To explain, let me show you some code I'd played with in irb:

def contFun2
puts "Is this fun?"
callcc { |x| return x }
return 'yes'
end

irb(main):048:0> z = contFun2
Is this fun?
=> #<Continuation:0xb7c99c14&rt;
irb(main):049:0> z
=> #<Continuation:0xb7c99c14&rt;

So far so good; when I call the function, I get a continuation from the middle of it. This continuation remembers things like variables and, most importantly for our purposes,the call stack. Now watch what happens when I use it:

irb(main):050:0> y = z.call
=> "yes"
irb(main):051:0> y
=> nil
irb(main):052:0> z
=> "yes"

You might be surprised to see that - z.call doesn't appear to return anything, and z itself somehow turned from a continuation to a string!

What actually happened behind the scenes is that, when I called the continuation, the stack as it was vanished, replaced with what it had been. And what had been was a call to my function, just after the callcc line. Thus, the function returns "yes", and that yes is assigned to z, because that assignment was on the stack before the function call. Once I understood what was going on there, it wasn't hard to make a new mission evaluator:


  • For every plot mission, find out if it's awardable:

    • Call mission.awardable? It'll return true, false, or, a [ condition, continuation ] pair - pop up the condition and when we're done with that, call the continuation. Repeat until we get true or false.


  • If it was awardable, award it.


    • Call mission.award. It'll return true or a [condition, continuation] pair. Handle this exactly as above.


  • There's no step three!



Tomorrow: Using all of this so you can get missions just from flying into a sector.