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.

Saturday, May 10, 2008

Day 100: Wild and Crazy Ride

100 days of programming! Barring a week where my sinuses were killing me and a month where I bought and moved into a new house, I've been programming pretty much nonstop.

Today's story is, I got quite a bit through having missions able to pop up information to the player, which is the primary way of getting the plot of the game across. I shall now explain how this came about.

First, a note: #127 was a minor bug that annoyed me. You'd click on a button in one state, and any buttons in that same location in the parent state would end up clicked too. I'll come back to that.

My main work was on #137. Presenting the player with messages when they're given a mission turned out to be very easy, so long as I was doing only that. It actually displayed the correct message the first time I ran the program, which almost never happens for anything. I was very happy!

I'd made the dialog box which displays this general enough that it could handle yes/no questions as well, so I figured I'd go on to the next part, which is having conditions ask questions. So you could land on a planet, get a question asking "Do you want to be recruited into this planet's defense force?", and if you answer yes you get the mission.

This worked pretty much first thing as well. I was quite happy, but noticed something awry. Specifically, I clicked 'yes' to the mission, but didn't get the follow-up "You accepted the mission" message I'd put in the test mission.

An aside: I'd unit tested everything I could about these new conditions and actions, but as they displayed items on screen and rely on the user for input, this turned out to not be much.

I ended up having to make the evaluator a full-fledged state rather than the 'plugin' sort of existence it had before, because it made handling the continuations that much easier. This ended up not helping and I'm considering reverting that particular change (because a plugin is /far/ more useful in more situations than a full-fledged state). I figured something was going wrong with my continuations. As mentioned before, I love continuations but I'll freely admit they're somewhat of a black art to me. I know enough to be dangerous and get some things done, but when it starts falling apart I'm not sure where to look.

Eventually, print statements are everywhere. What they tell me, in every single case, is that the message dialog is, in fact, being displayed, despite the fact that I can't see it. I spend a good full hour doing nothing but putting tracer code in my entire setup to find out when this is happening.

Suddenly, it worked! I immediately realized that I hadn't done anything. I frowned, horrible suspicion dawning in my mind. I ran the program again, clicked the button in the exact middle, and didn't see the message. One more time through, clicking the button off to the side where there was no corresponding button in the message displayer, however? That worked fine.

I'd spent an hour tracking down a bug I already knew existed. Thanks, #127.

The rest of my time was spent fixing #127, which proved surprisingly difficult. I figured that there was a situation where the mouse-up would trigger a state change before the mouse-up was done fully processing, and so the new state would get it. But that turned out to not be the case. My army of print statements helped here, because what I saw happen was something like this: (output made up because it's fixed now and I don't have anything to paste here)

Beginning event handling loop
Got mouse down event#<MouseDown #7544>
Got mouse up event #<MouseUp #4556>
State transitioned
Ended event handling loop
...
Beginning event handling loop
Got mouse down event#<MouseDown #7544>
Got mouse up event #<MouseUp #4556>

I wasn't getting duplicate mouse-ups that weren't handled, this was two separate times through the event loop, yet somehow I was getting the exact same events as before.

It was this that led me to the 'time machine' metaphor for continuations. They don't just return you to where they left off, they change the variables and such to be exactly like they were then. This is handy because otherwise I wouldn't be able to refer to them, but putting continuations in the middle of my event-handling loop ended up rewinding and then replaying it!

I fixed this by having the event loop just build an array of MouseUp events, then later - once outside that loop - going through them and calling the actual functions. It seems to work; that loop itself never gets rewound or if it does it's not causing the same problems.

But man! What is it about the stuff that's supposed to be easy that makes it so hard?

Friday, May 9, 2008

Day 99: Halo

Today's got a double dose of programming tips.

I started work today on #98, map feedback selection. That's because I wanted to work on #133, missions giving halos to sectors. The idea being that, if you're supposed to take cargo to a certain sector, the mission would highlight the map for you. It was a neat little action I thought I could do in a day. And I was right... mostly.

#98 was pretty easy - I made a Halo type which holds on to a color, and gave sectors the ability to hold onto them. When you click on a sector on the map, it's given the 'selection halo' and it's pretty obvious where you've clicked. That halo moves around when you pick new sectors, and is removed entirely when you leave the map screen (otherwise it'd persist in the save games!)

With 15 minutes to spare, I figured I might as well get started on the #133, and I wrote up the action and its associated unit test. They passed pretty easily (wasn't a difficult thing to code up). So I figured I'd put the theory into practice and add a little highlighting to my test mission. I go into the editor, add the action, save the scenario, then run the game. I get the mission.

No highlight. "Hmmm, that shouldn't be. I tested this!"

I look at the save game, and I've got the mission all right, but no halos have been attached to anything. I go back to the tests and note that my unit tests only test one "setup" action, and this particular mission has two. I add halos to the 'mission cycle' test to make sure they're happening when I get the mission.

Ah-ha! They're not! My previous halo tests had only tested whether or not they worked, not whether they were getting called at all. Certain now that I'd tracked down a bug where the mission was only doing the first bit of the setup, I swapped the order of the items and ran the tests again, only to find it still wasn't happening!

Finally, I looked into the code that awards the mission. That's where I found that my 'setup' code isn't being called at all.

Programming Tip of the Day: If your method isn't doing anything, try actually calling it.

I fix it. I start the game back up again, get the mission, pull up the map, and...

...

STILL NO HALOS!

I look at the save game, and at least this time they're actually being applied, so I can look somewhere other than the code I'd just fixed. I know that halos on the map work, because otherwise I wouldn't see any when I select sectors (and those are still showing up) - I look into the part where it sets up the buttons for the sectors to begin with and find out that, hey, it's not actually telling the buttons to have halos, thus the 'halo' array was empty.

Programming Tip of the Day: If there's nothing in your array, maybe it's because you're not adding anything.

After all that, they finally displayed, but weren't displaying correctly. A bug made them alternate halo colors, rather than display them in bands from the in-side out (the selection halo should always be on the outside, so the player can tell it's selected). It actually looked cooler than the intended result, but it would fail horribly on more than one halo, so it had to be fixed.

At least I got an extra 45 minutes of programming in.

Thursday, May 8, 2008

Day 98: Ticket Quota

Programming Tip of the Day: If your configuration changes don't actually change anything, make sure you're changing them on the right machine!

Today I finished the missions cycle; when you get a mission you can now complete it by fulfilling its ending requirements. This turned out to be pretty easy - I have a MissionEvaluator that helps out in this case. In the old TraderMission days, it was a full-fledged state, but that meant I could only complete (or give) missions whenever a state would transition, and not when the player's just flying around. Now, you could create a mission that gives players 1000 credits just for hanging out in a sector for a given amount of time.

I had a strategy when it came to putting in tickets to the tracker. I'd only do it if I came up with an idea that I couldn't do immediately (which is why milestone:Polish currently has 36 outstanding tickets) or at the end of a session so I'd remember what I was working on when I started up next.

I finished #123 at the very end of the session. And I found out that instead of creating no tickets, I created tickets for anything that I might want to do next. It's a much better way of putting tickets in the tracker, and I think does a lot for my productivity. Knowing what I'm going to work on next removes some of the fun. Now I've got tons to choose from!

Wait, that might not be a good thing.

Wednesday, May 7, 2008

Day 97: Retro

Let's talk UI.

So I finished my new Builder dialog today. You have two columns; on the left are items that are currently in the thing you're building (i.e. cargo for sale on a planet, weapons on a ship, etc), and on the right are all the things you have to choose from (all possible cargo, all possible weapons, etc). It's beautiful and works great. The reason I worked on it was so that it could replace the old build dialog which did the same thing more slowly. The old build dialog had an offshoot called the MarketDialog which I also intend to replace with this. So on the left the player will see all the cargo on their ship, and on the right they'll see all the cargo for sale.

So far so good. I went through the effort in this milestone because I didn't want to write the "Mission Computer" dialog in the old style when I was going to transition to the new style.

But - upon completion - it occurred to me that this is a horrible UI for choosing missions. Everything else: Cargo, weapons, addons, details don't especially matter (well they do, but only to a limited extent). Whereas with Missions, they drive the plot along. I need a preview area to allow all this text that describes them. Also, "Have/available" is not that great a layout for missions. With weapons, it's important to know that you have a missile launcher before stocking up on missiles. For missions? Your current missions don't matter at all; every mission is independent.

What I needed for missions, in other words, was one list to display them all on the left, a large preview area, and buttons on the right to accept the mission with.

That is, of course, exactly what the old UI did. Hooray for diversions!

Tuesday, May 6, 2008

Day 96: Rebuilding

The minichooser is done! It's tiny and choosing things.

I also fixed a few bugs, the upside of which is that I made my first fully-playable mission today, a fedex mission to deliver cargo to the next planet over. Thus I begin work on #123: letting the player go to the first planet and actually get the mission.

This presented me with a dilemma: I needed to write the "Mission Computer" dialog. I've written a number of dialogs like these before, and in pretty much all cases they're going to be phased out for the MiniBuilder and a new 'multiple add' dialog for it. So I could write something working right now based on the old framework and transition it, or I could put #123 on hold and create the new framework.

If you've been reading the blog so far, you'll know I'm a sucker for making changes unrelated to my current objective, so "On hold" it is! I created #130 for the new-and-improved builder. It'll essentially look like an FTP client, and will make moving multiple items to/from something very easy. The 'add' button on the minibuilder will use this, and once it's done, adding missions will be very simple indeed!

Monday, May 5, 2008

Day 95: Make Mine Mini

Today I made the first addition to the engine.rb file in a very long time; I moved the Waker module over to it. The Waker module is what implements continuations, which if I'd known about them when I originally started this project, would have been even more integrated into the engine.

Basically, here's what TraderMissions code for handling a dialog would look like, if TM was in Ruby:

# Called every time this state becomes
# the active one.
def activate
if @dialog.yes?
do_things
end
if @other_dialog.stuff?
do_other_things
end
end

The activate function has to check every possible state that it might transition into, see if it was called and if so what its result was, and deal with that. This rapidly becomes horrible. Whereas with continuations:

def activate
@continue.call if @continue
end

And that's set up when I originally transition to the other state:

def verify_cheese
cv = CheeseVerifier.new
callcc do | cont |
@continue = cont
@driver << cv
end

enable_cheese if cv.yes?
end

@continue.call moves execution back to after the block where I originally set @continue, thus letting me pick up where I had transitioned away from the current state. Hooray for continuations!

Oh, right, the subject. I'd actually done that continuation stuff months ago, the Waker thing was me moving it. Most of the day was me creating a simple object selector. I thought to myself "I need a MiniBuilder equivalent for those children which only have one object. Thus:

THE MINICHOOSER!

Sunday, May 4, 2008

Day 94: Medicated

Sinuses have been acting up these past few days, but now, as the title proclaims, I am medicated and much better. As such, I've gotten some stuff done:

  • #110 is done; the mission editor has minibuilders and they're very pretty and almost exactly how I envisioned them long ago when I drew the design.

  • #124 was quite a bit more work. The editor for Conditions and Actions doesn't act like any other editor, because you're never working on the base 'Condition' or 'Action' object. Rather, you select a subclass and work on that. Turned out to be pretty easy to have a list box on the left that selected the subclass, and the right is an appropriate editor.

    Actually, the 'appropriate editor' part being easy is half-right. The properties dialog, from which this descends, lays out fields (things like 'name', basic text/numeric stuff) without regard to what class it's doing it for. But children (i.e. other serializable objects) vary wildly and the properties dialog doesn't do anything with them, leaving it to subclasses to sort it out.

    Thus, I created a helper class to detect which subclass of KuiAction/KuiCondition it was working on, and lay out the children accordingly. This was annoyingly difficult, because I had to refactor all of the layout logic so a non-property dialog could use it, and then somehow shoehorn it back in. It's still a bit of a hack, but it gets the job done, and this is the only place I should need it.

  • Fixed a bug where scrolling in a list would appear to forget your current selection. Turns out it still remembers the selection behind the scenes, but it didn't bother re-painting it.


Finally, a helpful ruby tip. This:

case(object.class)
when KuiBlarg
setup_kui_blarg
end

Doesn't work how you might expect (i.e. at all). Instead, do this:

case(object)
when KuiBlarg
setup_kui_blarg
end