Showing posts with label ptotd. Show all posts
Showing posts with label ptotd. Show all posts

Sunday, November 9, 2008

Day 302: RTFS

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

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

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

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

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

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

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

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

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

Saturday, August 16, 2008

Day 217: Extended Rest

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

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


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

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

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

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

Wednesday, June 11, 2008

Day 151: It's Never Easy

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

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

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

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

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

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

The game would freeze.

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

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

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

player_ship.target = chosen if chosen

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

player_ship.target = chosen.model if chosen

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

Monday, June 2, 2008

Day 142: Two Hundred Hours

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

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

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

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

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

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

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!

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.

Saturday, April 26, 2008

Day 86

Programming Tip of the Day: There is a difference between '=' and '=='.

If you take a look back to yesterday's entry you might notice something about the minibuilder. Specifically, there are two entries in it that look almost the same: "wpn_lazor" and "wpn_lazor:2279". What that second one means is that it's been duplicated from the original wpn_lazor. That's the intended behavior; ships' weapons have to be duplicates of the original because the weapons have information about how long until they can fire again, etc, built into them. There was a bug early on where one ship got the weapons of every ship in the sector (because they weren't duplicates) and would fire an insane amount of stuff at you while the others flew around dumbly. So it's necessary.

But that screenshot illustrates a bug as well! You should never see the original wpn_lazor - wpn_lazor:2279 should instead internally have an amount of '2'. This is because they don't compare equal, and so the engine doesn't know to stack them.

As I delved into the weapons systems to get a unified feel, I kept discovering more and more kludges. The :weapons accessor only gives you primary weapons. Ammunition added to a ship somehow ends up getting registered as a primary weapon! I'm amazed this stuff works at all; it seemed pretty straightforward at the time.

Thus, today's work was almost entirely on bug #119: Weapon Unit tests. To make sure, once and for all, that they're working the way I expect. It feels like I'm going backwards (weapons were, after all, the stuff of the last milestone) but it needs to be done if I want the minibuilder to work, and I need the minibuilder for my current milestone.

I also created bug #118, wherein we warn the scenario editor if they're trying to put the player's ship up for sale. Because it's a ship like any other, and scenario creators might think "Hey, people might want to buy their original ship." If they did that, they'd get a ship that was exactly as damaged as their original ship! It does give me an idea, however: When you buy a new ship, the old one can be put up for sale in its old condition - that way you can always trade back.

Friday, April 25, 2008

Day 85

Programming Tip of the Day: If your sprite isn't rendering, try actually adding it to the screen!

I got a new server yesterday to replace the one that used to house my trac and SVN repository. I installed Ubuntu on it and tried to upgrade, only to find that this is taking forever. Turns out a new version came out mere hours before my server arrived. Not only was the install I had out of date, but due to everyone hammering the servers, it'll take forever to get up to date!

This gave me extra time to think about the blog post I made. I sat down with an actual honest-to-gods pen and paper and drew what I wanted the mission editor to look like. Then I was like "Hey, I can do that! And it'd be hugely useful to other objects, too!"

Thus, I introduce #117: THE MINIBUILDER!

As terrifyingly powerful as the old build dialog, but bite-sized!

All the functionality isn't there yet. I was halfway through writing up the subclass that would deal specifically with adding/removing weapons from ships (they have special methods to do this) when I thought to myself: Haven't I done this before?

Yes, I have. I made special subclasses of the old builder to handle weapons for ships... and cargo for ships... and addons for ships. The way ships handle them is different from the way planets handle them is different from the way that you'd just throw them in a list. Wouldn't it be nice, I thought, to have a unified API and then just adapt anything that doesn't conform to it?

Thus I created an Adapter class! It has an 'add_item', a remove_item, an 'items', and a 'constraint' (the type that goes into the list). I only have to write a 'Adding weapons to ships' subclass once instead of for every GUI element that does so. How useful, I thought! Then I went online to make sure I was spelling the word 'Adapter' correctly and discovered that my invention already exists and is a pattern. I still felt proud, having independently discovered it.

My server is currently formatting a 500GB disk whose reliability has been called into question. Thus I'm formatting it with the most slow and careful of bad-block checks. I do not recommend this unless you have a great deal of time on your hands, it's taken nearly 24 hours now. Still, if the disk is dodgy, this will find out. And if this doesn't break it, nothing will.

Wednesday, April 23, 2008

Day 83

I'd like to introduce a feature to this blog that I use at work quite a bit. It's called the Programming Tip of the Day, and it's for when I've made bone-headed mistakes. In Ruby, if you have a method like this:

def check
self.doImportantStuffHere
end

And then later in that same class you have a method like this:

def check
end

The second one takes precedence. Thus today's Programming Tip of the Day: If nothing's happening, make sure the method where stuff happens isn't being overwritten.

Today's major task was getting the mission cycle complete; I have a unit test which awards the player a mission (by asking the mission if it's appropriate and, if so, telling it to give itself to the player) and then tells that same mission to check to see if it's done. Given that the sole condition for completion is that the player be in the starting sector, it is done. This same check will automatically do any attached actions which, in this case, complete the mission.

I started work on the next action/condition group, which is to set and check 'flags'. The player has a set of flags which are essentially a big dictionary of key (the :tag of the flag) value (a string or number) pairs. Their intent is to be used in multi-leg missions: If you want the player to go from A to B to C, you set a flag when they get to B and then when they get to C check it to see if they jumped through the extra hoop.

I'm also trying out the google syntax highlighter for my code snippets above. We'll see how that turns out.

EDIT: Poorly!