Showing posts with label polish. Show all posts
Showing posts with label polish. Show all posts

Saturday, November 1, 2008

Day 294: NaGaFiMo

It's National Game Finishing Month!

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

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

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

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

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



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

Monday, September 15, 2008

Day 247: Express Yourself

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

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

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

Becomes:

Tag: player_best_player_world_city_arboretum

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

At first, I was doing something like this:

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

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

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

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


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

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

Sunday, September 14, 2008

Day 246: Checking In

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

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

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

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!

Tuesday, June 10, 2008

Day 150: On Automatic

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

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

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

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

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

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

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

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

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

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

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

Monday, June 9, 2008

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

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

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

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

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

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

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

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

Friday, June 6, 2008

Day 146: Blast from the Past

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

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

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

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

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

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

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

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

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

Tuesday, June 3, 2008

Day 143: Addons

First, the huge news: #149 is done!

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

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

It breaks things, is what!

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

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

Yeah. I forgot about that until now.

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!

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!

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.