Showing posts with label missions. Show all posts
Showing posts with label missions. Show all posts

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!

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

Monday, April 28, 2008

Day 88

Weapon unit tests: COMPLETED!
Minibuilder: COMPLETED!

That's the real content for today. The rest will be a love letter to unit testing.

Yesterday when I made the game run on windows, I had to tweak the XML handling - apparently on whatever version of ruby I'm running over there, attributes you give to XML bits aren't automatically made into strings like they are on my linux box. So I changed it to explicitly use to_s.

That worked fine until today, when I finished up the weapon unit tests. I ran the suite (I have a script that runs all of them) and found out that it failed, which wasn't a surprise until I discovered that it wasn't the weapons failing at all! It was older code that had broken with the above change. Turns out nil.to_s is '', and '' gets read back in as an empty string, which for my purposes is vastly different than nil. I solved this by having it only set non-nil attributes in the XML to begin with, but the point is I never would have found it until it bit me on the ass in unpredictable ways if not for testing.

But finding obscure places where I've broken my code isn't their only purpose! After finishing the weapons tests, I went back to the minibuilder (also done today!) and fleshed out the functionality. Removing was enabled, and I found it worked pretty much how I expected and how the tests predicted it would, except in one case: I couldn't remove secondary weapons.

I went back to the test cases to find that I'd never written a test for that! I did so and delighted to see that it failed. Found the problem, patched it up. In this case I would probably have tracked down the bug without the tests just fine, but now I'll be alerted if it ever happens again.

P.S. I like you, unit testing, do you like me?

43 tests, 140 assertions, 0 failures, 0 errors

I'm not hearing a no.... :)