Thursday, May 17, 2007

Conversations about Ukeleles

Paper session
 Well, that was a useful design session. I've got all sorts of new ideas for things I want to implement. The only downside is that there's a lot of work yet to be done. Things like NPCs (non-player characters), conversations, Objectives, Rooms, etc. etc.
 I've got a simple story that I'm going to work on for these examples. Over the next few (or, more likely, many) sessions, I'll work towards completing this mini-game. This example was inspired by my father-in-law's fondness of Hawaiian instruments.

Story
 You're at a Ukulele Noir concert and you have to convince the Stage Manager (named "Mike") to let you play.

Acronyms and Abbreviations
 I want to create a new class called NPC. I dislike using abbreviations in my code, as it's too small a gain (less typing) at two high a cost (confusion between "GetStats" or "GetStatistics" or other variations). I think CNonPlayerCharacter seems like a better option.
 Each NPC will have standard responses to three queries (from the Ultima series): "Name?", "Job?", and "Health?". They will also have an array of options for responses to Items and Abilities. I'll implement the standard three first, and then work on the more complex responses.

Test First!
 Ok, for the simple case, I need just one test: testAskStageManagerAboutBasicInfo. I've created the (empty) test and it passes. Now to make the test actually do something.

Shared Stage Manager
 By the way, I'm pretty sure that I'm going to want to add this stage manager NPC to the global parameters array. But I don't need it yet, so I'll code it inline for this first test and then extract it later.

Basic Info
 Test fails as CNonPlayerCharacter is not defined. Defining it now... passes. Adding Name... fails, as the name isn't set yet. I need an Initialize(strName) function. I'm going to be a little forward-looking and define it as Initialize(strName, strJob, strHealth). I'll just ignore the other parameters until I've implemented them.
 I also need a constructor to populate default values.
 Ah good - now the test is failing because the default value for Name doesn't match the expected value.
 Excellent, now the basic test for Name passes. I'll do the same for Job and Health. Unless anything interesting happens, I'll just say "Done"...Syntax error - nuts.
 Now, this is bizzare. The same test appears as passed in the detail view, but failed in the summary view. Ah, my logic for doing all three AssertStringsMatch test is to concatenate them instead of using a logic And. So the value is actually "truetruetrue". Clearly the detail view is looking for "not false", and the summary view is looking for "true". I'm torn between making both views consistent (which they should be), and leaving them as is (which would help to triangulate odd return values like "truetruetrue"). Laziness wins, I'll leave them as they are, and fix the test so that it really passes for real.
 Test passes, but it just tests Name three times - changing it to test each of the basic information fields. And it still passes. Excellent.

Conversation
 Now I want to make the stage manager respond to various prompts. Each prompt can either be an item, an "inherent" (e.g. not item-based) ability, or an item-based ability. It might also be cool to prompt the NPC with a statistic (e.g. "Look at how strong I am!"), but that's going on to the to-do list.
 Simple test first: testAskStageManagerAboutUkelele, but before I can do that, there's a problem I need to solve.

ItemID
 Interestingly enough, items do not have IDs yet (just Names). As I don't want to compare strings for identification, I'll have to add this first. Which means creating an ItemFactory (or, in my nomenclature, a CanonicalItems object). Let's see if I can make this change without breaking any tests.
 
Create CanonicalItems
 This is mostly a copy/paste of CanonicalAbilities. I may need to generalize CanonicalContainer. Again, added to the ToDo list.
 Ok, I've created CanonicalItems, but I haven't put any references to it anywhere yet. Let's just make sure it compiles. Ok, no obvious errors.
 Now, for each item, I want to put the item in the Canonical list, and then delegate any creation of that item to the new CanonicalItems object. Odd - I only find one.

Item: "Book of Knowledge"
 First thing to note (feel like we're going down into a rat's nest?) is that I create this book in two places - SetupCreateBookOfKnowledge and testCreateBook. I need to first finish SetupCreateBookOfKnowledge (adding the book to the arrParameters list) and then make testCreateBook use the object from arrParameters. Then I'll only have one object creation to change. This is a sub-refactoring (:
 Oh, hey, SetupCreateBookOfKnowledge already does the right thing. No changes there for this step, it's finished already. Just remains to repoint testCreateBook to use the passed-in book. Two paragraphs of description for two lines of code changes... and tests pass. (: Back up a level to moving the item creation to the CanonicalItems object.

Create Canonical Book of Knowledge
 Ok, I've updated the setup function, which should now fail (as the book isn't initialized). It fails a little harder than I would have liked (with Runtime error), but I'll ignore that for now, and make it pass.
 Boundary error...fixed... Didn't add ItemID to CItem (which is, after all, the entire point of this exercise). I would, if I could, make Set ItemID a package-level property, as I only want the canonical container to set it, but I don't have that option in this language (which is a recurring theme, unfortunately). Anyways, that's fixed, and all tests pass. Phew, now I can use ItemID when setting up conversational responses for the StageManager.

Ukelele
 First, let's create a Ukelele for us to discuss. I'll create a test for it, which is simply a slightly modified copy of testCreateBook.
 I made a bunch of changes. Let's see if they work...Nope, found another boundary error (which only occurs when there is more than 1 item). Fixed...Oops, copy/paste error. Fixed... All tests pass, but I didn't include the new testCreateUkelele. Adding...All tests pass.

More subrefactorings
 Now I can actually start writing testAskStageManagerAboutUkelele, right? No. The first thing I'm going this test to do is create another stage manager. But I've already done that in testAskStageManagerAboutBasicInfo. So I've got to extract that into SetupCreateStageManager() and add it to arrParameters.
 Step One: extract to SetupCreateStageManager. Done... tests pass (except for testAskStageManagerAboutUkelele, which isn't written yet).
 Step Two: make testAskStageManagerAboutBasicInfo use the StageManager in arrParameters(). Done. There's some duplication of values between the setup (which needs to set the values) and the test (which needs to check for them), but that's okay for now.
 Step Three: Make testAskStageManagerAboutUkelele use the new StageManager.

testAskStageManagerAboutUkelele
 The test is currently failing. I need to decide on a name for the object that contains the PromptItemID and the NPC's response. I think this collection in the NPC class can be called Script[], but what should the individual lines be? ScriptLine? ScriptDialogue? I'll go with ScriptLine for now, but may want to rename it later. (Ah, the freedom of refactoring).
 Ok, I've created a ScriptLine object, and now I need to create the collection inside the NPC, and add a response for Ukelele. Phew! I bet I'm missing some intermediate tests.
 Well, let's see what breaks... OK, no new errors. Great.
 Now I need to give the stage manager a script line for the Ukelele. I'll have to pass the Ukelele's ItemID to the setup function. I could pass the entire arrParameters, but I want to make this dependancy explicit.
 Ok, test should pass now - I didn't make a method public. Fixed... test fails, ah yes, I need to implement AskAboutItem. Implemented..Switched Index for Response..All tests pass.

Phew!
 That's a lot for today. Next steps include beefing up CScriptLine to suppose inquiries by AbilityID combined with ItemID, and then flushing those changes through all the functions I added today.