Skip to main content
  1. Posts/

Day 86 - Beholder on Your Phone pt4

OldDays sms-beholder ruby

Once more unto the breach.
Once more unto the breach.

In which this is already taking longer than I thought it would.

ConditionalSelection
#

When we quit yesterday, I promised to tackle conditionalSelection, since that’s what my “play the whole game” test case is currently choking on.

So what does that look like in the script?

script.json
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
   "M00": {
        "beholder": "mox",
        "beholderPos": "center",
        "beholderExp": "neutral",
        "bg": "SoccerField",
        "namePlate": ":name:",
        "conditionalSelection": {
            "condition": {
                "mox-kick": "known"
            },
            "constants": {
                "How do you like soccer?": "M08",
                "Do you like dancing?": "M08"
            },
            "ifConditionTrue": {
                "So I was wondering if you could show me that killer bicycle kick sometime...": "M01"
            }
        }
    },

There are some selections which are always displayed (the constants), so we’ll test that first.

Constants
#

The constant selections are easy enough to test:

spec/sms_beholder_spec.rb
86
87
88
89
90
  it 'shows constant selections from conditional selection set' do
    get '/', From: 'Dude', SkipToState: 'M00'

    expect(last_response.body).to include("1) How do you like soccer?\n2) Do you like dancing?")
  end

And pretty easy to implement:

sms_beholder.rb
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def self.selections(state)
  response = ''
  if @states[state] && @states[state].key?('selection')
    @states[state]['selection'].keys.each_with_index do |selection, index|
      response += "\n#{index + 1}) #{selection}"
    end
  end
  if @states[state] && @states[state].key?('conditionalSelection')
    @states[state]['conditionalSelection']['constants'].keys.each_with_index do |selection, index|
      response += "\n#{index + 1}) #{selection}"
    end
  end
  response
end

Not Constants
#

We can add these tests:

spec/sms_beholder_spec.rb
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
  it 'hides conditional selection without condition' do
    get '/', From: 'Dude', SkipToState: 'M00'

    expect(last_response.body).not_to include("that killer bicycle kick")
  end

  it 'hides conditional selection with failed condition' do
    get '/', From: 'Dude', SkipToState: 'M00', SetKey: 'mox-kick', SetKeyValue: 'unknown'

    expect(last_response.body).to include("1) How do you like soccer?\n2) Do you like dancing?")
    expect(last_response.body).not_to include("that killer bicycle kick")
  end

And get some nice false positives because the non-constant selections are being ignored. But that’ll catch us later if we start showing selections that we shouldn’t.

So now:

spec/sms_beholder_spec.rb
105
106
107
108
109
110
  it 'shows conditional selection with passed condition' do
    get '/', From: 'Dude', SkipToState: 'M00', SetKey: 'mox-kick', SetKeyValue: 'known'

    expect(last_response.body).to include("1) How do you like soccer?\n2) Do you like dancing?")
    expect(last_response.body).to include("3) So I was wondering if you could show me that killer bicycle kick")
  end

can be satisfied with this in the selections method:

sms_beholder.rb
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
    conditions_met = true
    @states[state]['conditionalSelection']['condition'].each_pair do | key, value |
      puts "Checking player #{player} for key #{key} value #{value}"
      if player[key] != value
        puts "nope"
        conditions_met = false
      else
        puts "yep" 
      end
    end

    if conditions_met
      @states[state]['conditionalSelection']['ifConditionTrue'].keys.each do |selection|
        response += "\n#{curr_selection}) #{selection}"
        curr_selection += 1
      end
    end

Selections refactor
#

Having shown the conditional selection, can we choose it? Not yet. We could add a completely separate check to see if a selection is valid according to the state of the player, but that would be needlessly repetitive. We could save and parse through the selection string, but that seems silly. Let’s just modify the selections method to return an array of selections.

sms_beholder.rb
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
    def self.selections(state, player)
      selections = []
      if @states[state] && @states[state].key?('selection')
        @states[state]['selection'].keys.each do |selection|
          selections << selection
        end
      end
      if @states[state] && @states[state].key?('conditionalSelection')
        @states[state]['conditionalSelection']['constants'].keys.each do |selection|
          selections << selection
        end

        conditions_met = true
        @states[state]['conditionalSelection']['condition'].each_pair do | key, value |
          puts "Checking player #{player} for key #{key} value #{value}"
          if player[key] != value
            puts "nope"
            conditions_met = false
          else
            puts "yep" 
          end
        end

        if conditions_met
          @states[state]['conditionalSelection']['ifConditionTrue'].keys.each do |selection|
            selections << selection
          end
        end

      end

      selections
    end
  end

Just a little mod to the way we consume the result, and we’re good.

sms_beholder.rb
125
126
127
128
129
130
131
  def process_output(player)
    state = player['state']

    response = GameStates.story(state)
    GameStates.selections(state, player).each_with_index do |selection, index|
      response += "\n#{index + 1}) #{selection}"
    end

Being relatively well-covered by unit tests, we can be confident in these refactors as long as the tests are still passing.

Choosing Conditional Selections
#

Now we’re ready for some tests.

spec/sms_beholder_spec.rb
112
113
114
115
116
117
  it 'allows conditional selection with passed condition' do
    get '/', From: 'Dude', SkipToState: 'M00', SetKey: 'mox-kick', SetKeyValue: 'known'
    get '/', From: 'Dude', Body: '3'

    expect(last_response.body).to include("Do you think you can keep up with me?")
  end

With a little more refactoring of the selections method to include the actual destination state (which I’ll leave out in the interest of brevity (too late)), we can satisy this one easily:

sms_beholder.rb
 98
 99
100
101
102
103
104
105
106
    if params.key?('SkipToState')
      state = params['SkipToState']
    elsif GameStates.states[state].key?('selection') || GameStates.states[state].key?('conditionalSelection')
      # handle using the input to choose selection
      if (index = body.to_i) != 0
        selections = GameStates.selections(state, player)
        if selections.length >= index
          state = selections[index - 1].last
        else

and a few more tests to tighten things up:

spec/sms_beholder_spec.rb
125
126
127
128
129
130
131
132
133
134
135
136
137
  it 'disallows conditional selection without condition' do | example |
    get '/', From: @dude, SkipToState: 'M00'
    get '/', From: @dude, Body: '3'

    expect(last_response.body).to include("1) How do you like soccer?")
  end

  it 'disallows conditional selection with failed condition' do
    get '/', From: @dude, SkipToState: 'M00', SetKey: 'mox-kick', SetKeyValue: 'unknown'
    get '/', From: @dude, Body: '3'

    expect(last_response.body).to include("1) How do you like soccer?")
  end

But what’s this @dude stuff? Oh yeah, I did a little more refactoring. I can explain. Tomorrow.


Useful Stuff
#


More to come
More to come

Day 86 code