Ran into a few bumps with duplicate Dude names in the tests, and didn’t want to have to manually
keep them unique, so wrote a quick before hook to make sure I have two (likely) unique Dude names
available to the test case.
…and we also get a nice marker at the beginning of each test case’s execution; there’s all sorts
of other stuff we could output or act on here:
Documentation for the Example class in Rspec.
Another refactor was the move the SetKey cheat into a method called process_misc so we can call
it for both new players (for whom we don’t process input yet) and existing players (for whom we
will process input).
defprocess_misc(params,player)ifparams.key?('SetKey')puts"setting key #{params['SetKey']} to #{params['SetKeyValue']}"player[params['SetKey']]=params['SetKeyValue']endplayerend
"S09":{"beholder":"siri","beholderPos":"left","beholderExp":"neutral","beholderAnimation":"fadeInLeft","bg":"Lunchroom","buttonText":"Next","namePlate":":name:","conditionalStory":{"condition":{"siri-smoked":"true"},"ifConditionTrue":{"story":"Sorry. I'm always nervous when we hang out. You're like too cool.","next":"S04","buttonText":"Next"},"ifConditionFalse":{"story":"But it's a really cool car!","next":"S10","buttonText":"Next"}}},
So now there are conditional story text and state transitions. A bunch more tests:
it'shows ifConditionFalse story without condition'doget'/',From:@dude,SkipToState:'S09'expect(last_response.body).toinclude("But it's a really cool car!")expect(last_response.body).not_toinclude("I'm always nervous when we hang out.")endit'shows ifConditionFalse story with false condition'doget'/',From:@dude,SkipToState:'S09',SetKey:'siri-smoked',SetKeyValue:'false'expect(last_response.body).toinclude("But it's a really cool car!")expect(last_response.body).not_toinclude("I'm always nervous when we hang out.")endit'shows ifConditionTrue story with true condition'doget'/',From:@dude,SkipToState:'S09',SetKey:'siri-smoked',SetKeyValue:'true'expect(last_response.body).toinclude("I'm always nervous when we hang out.")expect(last_response.body).not_toinclude("But it's a really cool car!")endit'proceeds to ifConditionFalse state without condition'doget'/',From:@dude,SkipToState:'S09'get'/',From:@dudeexpect(last_response.body).toinclude("Can you like, buzz off?")endit'proceeds to ifConditionFalse state with false condition'doget'/',From:@dude,SkipToState:'S09',SetKey:'siri-smoked',SetKeyValue:'false'get'/',From:@dudeexpect(last_response.body).toinclude("Can you like, buzz off?")endit'proceeds to ifConditionTrue state with true condition'doget'/',From:@dude,SkipToState:'S09',SetKey:'siri-smoked',SetKeyValue:'true'get'/',From:@dudeexpect(last_response.body).toinclude("Hehe. You're such a nerd")end
and some additions to the GameStates module to pass them:
defself.story(state,player)story=''if@states[state]&&@states[state].key?('story')story+=@states[state]['story']endif@states[state]&&@states[state].key?('conditionalStory')conditions_met=true@states[state]['conditionalStory']['condition'].each_pairdo|key,value|puts"Checking player #{player} for key #{key} value #{value}"ifplayer[key]!=valueputs'nope'conditions_met=falseelseputs'yep'endendifconditions_metstory+=@states[state]['conditionalStory']['ifConditionTrue']['story']elsestory+=@states[state]['conditionalStory']['ifConditionFalse']['story']endendstoryenddefself.next(state,player)next_state=nilif@states[state]&&@states[state].key?('story')next_state=@states[state]['next']endif@states[state]&&@states[state].key?('conditionalStory')conditions_met=true@states[state]['conditionalStory']['condition'].each_pairdo|key,value|puts"Checking player #{player} for key #{key} value #{value}"ifplayer[key]!=valueputs'nope'conditions_met=falseelseputs'yep'endendifconditions_metnext_state=@states[state]['conditionalStory']['ifConditionTrue']['next']elsenext_state=@states[state]['conditionalStory']['ifConditionFalse']['next']endendnext_stateend
Looks pretty repetitive, and we may
DRY it up sooner than
later. But
it does work.
In order to get the “can be played all the way through” test case to
work, we had to allow for states that don’t actually have a story key. The fix was trivial, but
the important part is that we added a test for it:
it'can handle states with no story'doget'/',From:@dude,SkipToState:'DAY2'get'/',From:@dudeexpect(last_response.body).toinclude("You arrive at school early")end
"S03":{"beholder":"siri","beholderPos":"center","beholderExp":"shy","beholderAnimation":"fadeIn","bg":"Lunchroom","buttonText":"Success","namePlate":"Siriak the Rapid","story":"Yeah, well... I was planning on hanging out around this lame dance. There's nothing else to do tonight. You want to come?","next":"SIRIWIN"},
"S02":{"beholder":"siri","beholderPos":"left","beholderExp":"neutral","beholderAnimation":"fadeIn","bg":"Lunchroom","namePlate":":name:","conditionalStory":{"condition":{"siri-smoked":"true"},"ifConditionTrue":{"story":"Yeah. Or we could just hang out by the dumpster. Maybe just you and me this time?","next":"S03","buttonText":"Next"},"ifConditionFalse":{"story":"Sweet! I love beer!","next":"S04","buttonText":"Next"}}},
"D1S307":{"bg":"Dumpster","story":"Someone offers you the cigarette, and you can feel Siriak's gaze drawn down upon you. You draw, hold it, and exhale, all without erupting into a fit of coughing. They think you're cool. Siriak thinks you're cool. And you've made a fool of the surgeon general.","next":"DAY2","buttonText":"Next","beholder":"siri","beholderPos":"center","beholderExp":"neutral","beholderAnimation":"fadeIn","plotPoint":{"siri-smoked":"true"}},
Oh, it’s a plotPoint. I’m not handling plotPoints, yet.
it'sets conditions at plot points'doget'/',From:@dude,SkipToState:'D1S307'get'/',From:@dude,SkipToState:'S02'expect(last_response.body).toinclude("hang out by the dumpster.")end
defprocess_output(player)state=player['state']response=GameStates.story(state,player)GameStates.selections(state,player).each_with_indexdo|selection,index|response+="\n#{index+1}) #{selection.first}"endGameStates.plot_points(state).each_pairdo|key,value|player[key]=valueputs"Plot point set player key #{key} to #{value}, and now player is #{player}"end
and there we have it. It may seem weird to have put the plot points in process_output, but they
are an output of the state (just not one that is directly exposed to the player).
it'can be played to a good ending'doget'/',From:@dudeTimeout::timeout(10)dountillast_response.body.include?('Royalty does not concern itself with common dances.')||last_response.body.include?('True power is slow dancing with someone who could beat you senseless.')||last_response.body.include?('Thirst for knowledge. Hunger for power. No feast is fine enough.')do@dude+='0'iflast_response.body.include?('And you won\'t be alone...')get'/',From:@dude,Body:['1','2','3'].sampleputslast_response.bodyendendend
When we hit a bad ending, we just add a zero to the end of the player ID and we get restarted. We
also test the bad ending the same way.