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