Friday, December 24, 2010

Red-Green-Refactor in Real Life, part 1

I would never have achieved my initial progress with Ruby'Ai without lessons from The RSpec Book. Only the direction and focus provided by the Behavior-Driven Development (BDD) and Test-Driven Development (TDD) methodologies enabled me to work through the ambiguities of a new project like this, but some of the book's lessons had to wait until this month's alpha-build stage before taking properly.

Just last night, I finally got a handle on the two-stage Red-Green-Refactor (RGR) process enabled by Cucumber, which lets me share a real-life example of the process with all of you. As context, Ruby'Ai uses a domain-specific language (DSL) to describe visual novels (playable here and implemented here). For example (all source code: rgr_part_1.tar.gz, rgr_part_1.zip):
scripts/characters.rb
add_character :sam, "Samantha"
add_character :courtney, "Courtney"
scripts/scenes.rb
add_scene :first_day_of_class do
show tired_sam
narrate "The class bell rings, immediately lost underneath the coordinated shuffling of papers and the shifting of chairs."
sam "sighs at the sight of clock arms that signal an imminent lunch before leaning back in her chair to address the girl standing beside her."
show content_courtney
grateful_sam "You saved my life, really. It's Courtney, right?"
pleased_courtney "Don't worry about it, Sam, okay? I printed off extras for everybody, just in case."
end
I had already implemented some of the basics, like add_character and add_scene, leaving some unexplained code intermingled with the important parts, but these examples will focus on where the RGR process proper started with the description of scene contents. From an extract of the BDD document:
features/scenes.feature
Feature:
In order to have an engaging story
As a visual novel writer
I want to have scenes in my novel
Scenario: minimal scene
Given a character labeled :sam and named "Samantha"
And a scene with following contents:
"""
sam says "Hello!"
smiling_sam "waves enthusiastically."
"""
Then the scene should have the following steps:
| condition | character | behavior | content |
| default | sam | statement | Hello! |
| smiling | sam | action | waves enthusiastically |
To explain the Then step: Ruby'Ai only provides an engine by which plugins export playable games, such as the Javascript-based prototype, so Ruby'Ai stores convertible steps for each part of the novel. These steps describe character speech, actions, narration, changes of location, and other events in a scene, one inseparable section of story and a close analogue to the screenplay equivalent. The condition describes the state of the character as they perform the action, by which the game chooses an image for the character (e.g. sam_smiling.png); the character names the involved character; the behavior describes the nature of the event (which may affect formatting); finally, the content describes what text the player will see associated with the rest of the step.

My major realization manifested here, the point right after writing the above feature example, when I remembered to take the development process directly to the lower-level Rspec stage. Having fallen in love with Cucumber early in the prototyping stage, I had come to rely on it almost exclusively and ended up writing pages of verbose feature lists. These grew unmanageable as they tried to describe every facet of the DSL and export process. The tool eventually interfered with my ability to further develop and debug the engine once I started spending as much time hunting through the output for the error messages as I spent addressing them. Almost all of this example code should have gone into the Rspec test fixtures, it turned out, but this next example from the alpha demonstrates a more proportional usage of these tools. It is a long example, but many of the parts overlap and we will break these parts down piece-by-piece in the second post of this series. Additional explanation available at the end.
spec/scene_spec.rb
require 'rspec/expectations' require 'novel' describe Scene do before(:each) do @novel = Novel.new @novel.evaluate_character_script do add_character :sam, "Samantha" end @novel.add_scene :at_the_park end it "should let a character say something explicitly" do @novel.evaluate_scene_script :at_the_park do sam says "So what do we do now?" end step = @novel.scenes[:at_the_park].steps.first step.should_not == nil step.target.should == @novel.characters[:sam] step.behavior_type.should == :statement step.content.should == "So what do we do now?" end it "should let a character do something explicitly" do @novel.evaluate_scene_script :at_the_park do sam thus "throws a frisbee." end step = @novel.scenes[:at_the_park].steps.first step.should_not == nil step.target.should == @novel.characters[:sam] step.behavior_type.should == :action step.content.should == "throws a frisbee." end it "should recognize conditions added to characters" do @novel.evaluate_scene_script :at_the_park do excited_sam "catches the frisbee as it's thrown back!" end step = @novel.scenes[:at_the_park].steps.first step.should_not == nil step.target.should == @novel.characters[:sam] step.behavior_type.should == :action step.content.should == "catches the frisbee as it's thrown back!" step.condition.should == :excited end end describe SceneBuilder do before(:each) do @character = Character.new :label => :sam, :name => "Samantha" @scene = Scene.new @builder = SceneBuilder.new :characters => { :sam => @character }, :scene => @scene end it "should mark explicitly stated content as such" do statement = @builder.says "I like it here, it's so cozy." statement.behavior_type.should == :statement end it "should mark explicit action content as such" do statement = @builder.thus "settled into the couch." statement.behavior_type.should == :action end it "should interpret behavior types implicitly" do @builder.parse_character_behavior @character, "Can I stay a bit longer?" @builder.scene.steps[0].behavior_type.should == :statement @builder.parse_character_behavior @character, "pleads convincingly." @builder.scene.steps[1].behavior_type.should == :action end end
SceneBuilder might appear redundant but it provides a clear delineation between the Scene - which only needs to store information about part of the novel - and the methods by which that information gets into the novel. For comparison, we do not need to know anything about the workings of a farm upon which an apple is grown to enjoy it and including farming instructions pasted all over the apple would only make it harder to eat. This approach may also facilitate a clearer implementation of the DSL code down the road.

Wednesday, February 3, 2010

iPhone Project Timeline (WIP)

I know better than to trust my own organic memory with this task, so here is an index of what has gone into the iPhone programming project thus far:

Stage One: Childish indiscretion
  1. iPhone programming contest announced.
  2. Ordered iPhone and Cocoa development books from The Pragmatic Programmers. Read a bit of the first in my spare time.
  3. Contest start time arrived. Started reading in earnest, beginning on the basic tutorials once the development Mac was set up in the office (all while leaving time for my daily duties).
  4. Brainstormed various project ideas.
Stage Two: Youthful vigor exerted and discovering the world at large
  1. Researched the implementation details of said ideas, filtering out those which the technology and contest deadline made impractical.
  2. Staunchly decided to go with a promising but time-pragmatic idea, expressly avoiding any thoughts of trying other projects in order to avoid a "paralysis of choice" situation.
  3. Prepared multiple paper-based mockups of the layout and received coworker feedback on the now-visible idea.
  4. Began coding a mockup in earnest.
  5. Worked through the setup details necessary to load the app onto the developer iPhone hardware itself (as opposed to just the simulator).
  6. Developed rudimentary implementations of the front-end GUI and online data-loading components, resulting in a presentable prototype.
  7. With said prototype in hand, began demonstrating it eagerly to others and started soliciting coworkers as developer teammates while expanding and refactoring the prototype.
Stage Three: The college years
  1. Met with a handful of coworkers over coffee, making a point of only inviting as many as I could hope to handle so as to avoid a "Too many cooks" scenario. Discovered just how nebulous of a project plan I had.
  2. Assigned a high-level project component to each teammate, divided by functionality and with the hopes of avoiding unnecessary overlap (and the cross-coding that would result). The specific groupings: Backend GUI, Web-based data input, and Web-based data output.
  3. Discovered the primary, extra-narrow purpose for the app, one with more personal value.
  4. Worked out per-team member lists of actionable items. The early items represent comprehensible milestones that, with hope, elicit distinct actions on their owners' parts. Expressed the intent to further subdivide these actions as necessary.
  5. Stayed on-hand one evening in case the Backend GUI developer needed any help with the initial iPhone tutorials. (He needed no such help, though, and knocked it right out.)
  6. Wrote this blog post.
I'll add to this chronology in subsequent posts as the project progresses, especially as the contest deadline approaches.