Yesterday’s post was way too long; sorry about that. I’m not sure if today’s will be shorter.
Dude Refactor
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.
before(:example) do| example |puts"\n*** #{example.location} ***\n"
@dude ="Dude"+ example.id.hash.to_s
@dude2 ="Dude"+ (example.id.hash +1).to_s
end
…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.
Misc Process
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)
if params.key?('SetKey')
puts"setting key #{params['SetKey']} to #{params['SetKeyValue']}"
player[params['SetKey']]= params['SetKeyValue']end
player
end
Conditional Story?
Trying my “can be played all the way through” test again, I get stuck
on state S09:
it 'shows ifConditionFalse story without condition'do
get '/', From: @dude, SkipToState: 'S09'
expect(last_response.body).to include("But it's a really cool car!")
expect(last_response.body).not_to include("I'm always nervous when we hang out.")
end
it 'shows ifConditionFalse story with false condition'do
get '/', From: @dude, SkipToState: 'S09', SetKey: 'siri-smoked', SetKeyValue: 'false'
expect(last_response.body).to include("But it's a really cool car!")
expect(last_response.body).not_to include("I'm always nervous when we hang out.")
end
it 'shows ifConditionTrue story with true condition'do
get '/', From: @dude, SkipToState: 'S09', SetKey: 'siri-smoked', SetKeyValue: 'true'
expect(last_response.body).to include("I'm always nervous when we hang out.")
expect(last_response.body).not_to include("But it's a really cool car!")
end
it 'proceeds to ifConditionFalse state without condition'do
get '/', From: @dude, SkipToState: 'S09'
get '/', From: @dude
expect(last_response.body).to include("Can you like, buzz off?")
end
it 'proceeds to ifConditionFalse state with false condition'do
get '/', From: @dude, SkipToState: 'S09', SetKey: 'siri-smoked', SetKeyValue: 'false'
get '/', From: @dude
expect(last_response.body).to include("Can you like, buzz off?")
end
it 'proceeds to ifConditionTrue state with true condition'do
get '/', From: @dude, SkipToState: 'S09', SetKey: 'siri-smoked', SetKeyValue: 'true'
get '/', From: @dude
expect(last_response.body).to include("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_pair do|key, value|puts"Checking player #{player} for key #{key} value #{value}"if player[key]!= value
puts'nope'
conditions_met =falseelseputs'yep'endendif conditions_met
story += @states[state]['conditionalStory']['ifConditionTrue']['story']else
story += @states[state]['conditionalStory']['ifConditionFalse']['story']endend
story
enddefself.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_pair do|key, value|puts"Checking player #{player} for key #{key} value #{value}"if player[key]!= value
puts'nope'
conditions_met =falseelseputs'yep'endendif conditions_met
next_state = @states[state]['conditionalStory']['ifConditionTrue']['next']else
next_state = @states[state]['conditionalStory']['ifConditionFalse']['next']endend
next_state
end
Looks pretty repetitive, and we may
DRY it up sooner than
later. But
it does work.
Storyless States
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'do
get '/', From: @dude, SkipToState: 'DAY2'
get '/', From: @dude
expect(last_response.body).to include("You arrive at school early")
end
Bad Endings
We can happily leave the “can be played all the way through” test enabled now, but something is
troubling.
It looks like you'll be going to the Big Dance after all. And you won't be alone...
It always hits the “Bad” ending. Despite its random choices, it get this ending every single time.
Well, let’s find a “Good” ending and work backwords to what is required. Spoiler alert.
"SIRIWIN": {
"bg": "siri-splash",
"story": "This car is a castle. The passenger seat is your throne. Royalty does not concern itself with common dances."
},
"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'do
get '/', From: @dude, SkipToState: 'D1S307'
get '/', From: @dude, SkipToState: 'S02'
expect(last_response.body).to include("hang out by the dumpster.")
end
defprocess_output(player)
state = player['state']
response =GameStates.story(state, player)
GameStates.selections(state, player).each_with_index do|selection, index|
response +="\n#{index +1}) #{selection.first}"endGameStates.plot_points(state).each_pair do|key, value|
player[key]= value
puts"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).
Good Endings
Rather than just rerunning the tests a bunch of times, let’s actually seek out a good ending:
it 'can be played to a good ending'do
get '/', From: @dude
Timeout::timeout(10) dountil last_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'if last_response.body.include?('And you won\'t be alone...')
get '/', From: @dude, Body: ['1','2','3'].sample
puts last_response.body
endendend
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.