gamefic-standard 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/.vscode/launch.json +20 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +60 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/gamefic-standard.gemspec +44 -0
- data/lib/gamefic-standard.rb +10 -0
- data/lib/gamefic-standard/actions.rb +13 -0
- data/lib/gamefic-standard/actions/drop.rb +21 -0
- data/lib/gamefic-standard/actions/enter.rb +21 -0
- data/lib/gamefic-standard/actions/go.rb +56 -0
- data/lib/gamefic-standard/actions/insert.rb +38 -0
- data/lib/gamefic-standard/actions/inventory.rb +11 -0
- data/lib/gamefic-standard/actions/leave.rb +32 -0
- data/lib/gamefic-standard/actions/look.rb +117 -0
- data/lib/gamefic-standard/actions/nil.rb +38 -0
- data/lib/gamefic-standard/actions/place.rb +43 -0
- data/lib/gamefic-standard/actions/quit.rb +14 -0
- data/lib/gamefic-standard/actions/take.rb +33 -0
- data/lib/gamefic-standard/actions/talk.rb +29 -0
- data/lib/gamefic-standard/actions/wait.rb +5 -0
- data/lib/gamefic-standard/articles.rb +50 -0
- data/lib/gamefic-standard/clothing.rb +4 -0
- data/lib/gamefic-standard/clothing/actions.rb +4 -0
- data/lib/gamefic-standard/clothing/actions/doff.rb +14 -0
- data/lib/gamefic-standard/clothing/actions/drop.rb +10 -0
- data/lib/gamefic-standard/clothing/actions/inventory.rb +16 -0
- data/lib/gamefic-standard/clothing/actions/wear.rb +22 -0
- data/lib/gamefic-standard/clothing/entities.rb +7 -0
- data/lib/gamefic-standard/clothing/entities/clothing.rb +5 -0
- data/lib/gamefic-standard/clothing/entities/coat.rb +3 -0
- data/lib/gamefic-standard/clothing/entities/gloves.rb +3 -0
- data/lib/gamefic-standard/clothing/entities/hat.rb +3 -0
- data/lib/gamefic-standard/clothing/entities/pants.rb +3 -0
- data/lib/gamefic-standard/clothing/entities/shirt.rb +3 -0
- data/lib/gamefic-standard/clothing/entities/shoes.rb +3 -0
- data/lib/gamefic-standard/container.rb +27 -0
- data/lib/gamefic-standard/direction.rb +55 -0
- data/lib/gamefic-standard/edible.rb +23 -0
- data/lib/gamefic-standard/entities.rb +10 -0
- data/lib/gamefic-standard/entities/character.rb +11 -0
- data/lib/gamefic-standard/entities/fixture.rb +3 -0
- data/lib/gamefic-standard/entities/item.rb +5 -0
- data/lib/gamefic-standard/entities/portal.rb +44 -0
- data/lib/gamefic-standard/entities/receptacle.rb +3 -0
- data/lib/gamefic-standard/entities/room.rb +79 -0
- data/lib/gamefic-standard/entities/rubble.rb +11 -0
- data/lib/gamefic-standard/entities/scenery.rb +7 -0
- data/lib/gamefic-standard/entities/supporter.rb +7 -0
- data/lib/gamefic-standard/entities/thing.rb +72 -0
- data/lib/gamefic-standard/give.rb +27 -0
- data/lib/gamefic-standard/grammar.rb +2 -0
- data/lib/gamefic-standard/grammar/attributes.rb +37 -0
- data/lib/gamefic-standard/grammar/pronoun.rb +101 -0
- data/lib/gamefic-standard/lockable.rb +93 -0
- data/lib/gamefic-standard/modules.rb +7 -0
- data/lib/gamefic-standard/modules/enterable.rb +19 -0
- data/lib/gamefic-standard/modules/use.rb +45 -0
- data/lib/gamefic-standard/openable.rb +49 -0
- data/lib/gamefic-standard/pathfinder.rb +65 -0
- data/lib/gamefic-standard/queries.rb +25 -0
- data/lib/gamefic-standard/test.rb +36 -0
- data/lib/gamefic-standard/version.rb +5 -0
- metadata +182 -0
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'gamefic-standard/entities/thing'
|
2
|
+
require 'gamefic-standard/entities/character'
|
3
|
+
require 'gamefic-standard/entities/fixture'
|
4
|
+
require 'gamefic-standard/entities/item'
|
5
|
+
require 'gamefic-standard/entities/portal'
|
6
|
+
require 'gamefic-standard/entities/receptacle'
|
7
|
+
require 'gamefic-standard/entities/room'
|
8
|
+
require 'gamefic-standard/entities/scenery'
|
9
|
+
require 'gamefic-standard/entities/rubble'
|
10
|
+
require 'gamefic-standard/entities/supporter'
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class Portal < Thing
|
2
|
+
attr_accessor :destination
|
3
|
+
|
4
|
+
# Find the portal in the destination that returns to this portal's parent
|
5
|
+
#
|
6
|
+
# @return [Room]
|
7
|
+
def find_reverse
|
8
|
+
return nil if destination.nil?
|
9
|
+
rev = direction.reverse
|
10
|
+
if rev != nil
|
11
|
+
destination.children.that_are(Portal).each { |c|
|
12
|
+
if c.direction == rev
|
13
|
+
return c
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the ordinal direction of this Portal
|
21
|
+
# Portals have distinct direction and name properties so games can display a
|
22
|
+
# bare compass direction for exits, e.g., "south" vs. "the southern door."
|
23
|
+
#
|
24
|
+
# @return [Direction]
|
25
|
+
def direction
|
26
|
+
@direction
|
27
|
+
end
|
28
|
+
|
29
|
+
def direction= d
|
30
|
+
@direction = Direction.find(d)
|
31
|
+
end
|
32
|
+
|
33
|
+
def name
|
34
|
+
@name || (direction.nil? ? destination.name : direction.name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def instruction
|
38
|
+
direction || (destination ? "to #{destination.definitely}" : name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def synonyms
|
42
|
+
"#{super} #{@destination} #{@direction} #{!direction.nil? ? direction.synonyms : ''}"
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class Room < Thing
|
2
|
+
attr_writer :explicit_exits
|
3
|
+
|
4
|
+
set_default explicit_exits: true
|
5
|
+
|
6
|
+
def explicit_exits?
|
7
|
+
@explicit_exits
|
8
|
+
end
|
9
|
+
|
10
|
+
def synonyms
|
11
|
+
@synonyms.to_s + " around here room"
|
12
|
+
end
|
13
|
+
|
14
|
+
def tell(message)
|
15
|
+
children.each { |c|
|
16
|
+
c.tell message
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_portal(direction)
|
21
|
+
d = direction.to_s
|
22
|
+
portals = children.that_are(Portal).delete_if { |p| p.direction.to_s != d }
|
23
|
+
portals[0]
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
def explicit_exits?
|
28
|
+
default_attributes[:explicit_exits]
|
29
|
+
end
|
30
|
+
|
31
|
+
def explicit_exits=(bool)
|
32
|
+
set_default explicit_exits: bool
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @todo Monkey patching might not be the best way to handle this. It's only
|
38
|
+
# necessary because of specs that make Plot#connect calls. Consider
|
39
|
+
# changing the specs instead.
|
40
|
+
module Gamefic::World
|
41
|
+
# Create portals between rooms.
|
42
|
+
#
|
43
|
+
# @return [Portal]
|
44
|
+
def connect origin, destination, direction = nil, type: Portal, two_way: true
|
45
|
+
if direction.nil?
|
46
|
+
portal = make type, :parent => origin, :destination => destination
|
47
|
+
if two_way == true
|
48
|
+
portal2 = make type, :parent => destination, :destination => origin
|
49
|
+
end
|
50
|
+
else
|
51
|
+
if direction.kind_of?(String)
|
52
|
+
direction = Direction.find(direction)
|
53
|
+
end
|
54
|
+
portal = make type, :direction => direction, :parent => origin, :destination => destination
|
55
|
+
portal.proper_named = true if type == Portal
|
56
|
+
if two_way == true
|
57
|
+
reverse = direction.reverse
|
58
|
+
if reverse == nil
|
59
|
+
raise "#{direction.name.cap_first} does not have an opposite direction"
|
60
|
+
end
|
61
|
+
portal2 = make type, :direction => reverse, :parent => destination, :destination => origin
|
62
|
+
portal2.proper_named = true if type == Portal
|
63
|
+
end
|
64
|
+
end
|
65
|
+
portal
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
Room.module_exec self do |plot|
|
70
|
+
# Define the connect method dynamically so the plot is available
|
71
|
+
define_method :connect do |destination, direction = nil, type: Portal, two_way: true|
|
72
|
+
plot.connect self, destination, direction, type: Portal, two_way: true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
class Room
|
76
|
+
# @!method connect destination, direction = nil, type: Portal, two_way: true
|
77
|
+
# Create a portal to connect this room to a destination.
|
78
|
+
# @return [Portal]
|
79
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# script 'standard/entities/scenery'
|
2
|
+
require 'gamefic-standard/entities/scenery'
|
3
|
+
|
4
|
+
# Rubble is Scenery with slightly modified action responses.
|
5
|
+
# Intended for things that might be portable but are useless.
|
6
|
+
# Rule of thumb: Scenery is something that can't be carried,
|
7
|
+
# like a table or the sky; and Rubble is something that might
|
8
|
+
# be portable but is otherwise useless, like trash or debris.
|
9
|
+
#
|
10
|
+
class Rubble < Scenery
|
11
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
class Thing < Gamefic::Entity
|
2
|
+
include Grammar::Attributes
|
3
|
+
|
4
|
+
attr_writer :itemized
|
5
|
+
|
6
|
+
attr_writer :sticky
|
7
|
+
|
8
|
+
attr_writer :portable
|
9
|
+
|
10
|
+
# An optional description to use when itemizing entities in room
|
11
|
+
# descriptions. The locale_description will be used instead of adding
|
12
|
+
# the entity's name to a list.
|
13
|
+
#
|
14
|
+
attr_accessor :locale_description
|
15
|
+
|
16
|
+
# A message to be displayed in response to DROP actions when the entity is
|
17
|
+
# sticky.
|
18
|
+
#
|
19
|
+
attr_accessor :sticky_message
|
20
|
+
|
21
|
+
set_default itemized: true
|
22
|
+
set_default sticky: false
|
23
|
+
set_default portable: false
|
24
|
+
|
25
|
+
# Itemized entities are automatically listed in room descriptions.
|
26
|
+
#
|
27
|
+
# @return [Boolean]
|
28
|
+
def itemized?
|
29
|
+
@itemized
|
30
|
+
end
|
31
|
+
|
32
|
+
# Sticky entities cannot be dropped with DROP actions
|
33
|
+
#
|
34
|
+
# @return [Boolean]
|
35
|
+
def sticky?
|
36
|
+
@sticky
|
37
|
+
end
|
38
|
+
|
39
|
+
# Portable entities can be taken with TAKE actions.
|
40
|
+
#
|
41
|
+
# @return [Boolean]
|
42
|
+
def portable?
|
43
|
+
@portable
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Boolean]
|
47
|
+
def attached?
|
48
|
+
@attached ||= false
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param [Boolean]
|
52
|
+
def attached= bool
|
53
|
+
bool = false if parent.nil?
|
54
|
+
@attached = bool
|
55
|
+
end
|
56
|
+
|
57
|
+
def parent= p
|
58
|
+
self.attached = false unless p == parent
|
59
|
+
super
|
60
|
+
end
|
61
|
+
|
62
|
+
# The entity's parent room (i.e., the closest ascendant that is a Room).
|
63
|
+
#
|
64
|
+
# @return [Room]
|
65
|
+
def room
|
66
|
+
p = parent
|
67
|
+
until p.is_a?(Room) or p.nil?
|
68
|
+
p = p.parent
|
69
|
+
end
|
70
|
+
p
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# @gamefic.script standard/give
|
2
|
+
|
3
|
+
respond :give, Use.available, Gamefic::Query::Children.new do |actor, _character, gift|
|
4
|
+
actor.tell "Nothing happens."
|
5
|
+
end
|
6
|
+
|
7
|
+
respond :give, Use.available(Character), Use.available do |actor, character, gift|
|
8
|
+
if gift.sticky?
|
9
|
+
actor.tell gift.sticky_message || "You need to keep #{the gift} for now."
|
10
|
+
else
|
11
|
+
actor.tell "#{The character} doesn't want #{the gift}."
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
respond :give, Use.available(Character), Use.available do |actor, _character, gift|
|
16
|
+
if gift.parent == actor
|
17
|
+
actor.proceed
|
18
|
+
else
|
19
|
+
actor.tell "You don't have #{the gift}."
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
respond :give, Use.text, Use.available do |actor, character, _gift|
|
24
|
+
actor.tell "You don't see any \"#{character}\" here."
|
25
|
+
end
|
26
|
+
|
27
|
+
interpret "give :gift to :character", "give :character :gift"
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Grammar
|
2
|
+
# A collection of attributes that enable grammar features for entities, such
|
3
|
+
# as selecting their correct pronouns.
|
4
|
+
#
|
5
|
+
module Attributes
|
6
|
+
# @see #gender
|
7
|
+
attr_writer :gender
|
8
|
+
|
9
|
+
# @see #plural?
|
10
|
+
attr_writer :plural
|
11
|
+
|
12
|
+
# The gender of the object. Supported values are :male, :female, :neutral,
|
13
|
+
# and :other. Use :neutral for objects that don't have a gender (i.e.,
|
14
|
+
# "it"). Use :other for people or characters that have an unspecified or
|
15
|
+
# non-binary gender (i.e., "they").
|
16
|
+
#
|
17
|
+
# @return [Symbol]
|
18
|
+
def gender
|
19
|
+
@gender ||= :neutral
|
20
|
+
end
|
21
|
+
|
22
|
+
# True if the object should be referred to in the plural, e.g., "they"
|
23
|
+
# instead of "it."
|
24
|
+
# @return [Boolean]
|
25
|
+
#
|
26
|
+
def plural?
|
27
|
+
@plural ||= false
|
28
|
+
end
|
29
|
+
|
30
|
+
# For now, the object's person is always assumed to be third
|
31
|
+
# (he/she/it/they). A future version of this library might support first
|
32
|
+
# (I/me) and second (you).
|
33
|
+
def person
|
34
|
+
3
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grammar
|
4
|
+
# Functions to select pronouns based on an entity's attributes, such as
|
5
|
+
# gender.
|
6
|
+
#
|
7
|
+
module Pronoun
|
8
|
+
class << self
|
9
|
+
# @param entity [#person, #plural?, #gender]
|
10
|
+
# @return [String]
|
11
|
+
def subjective(entity)
|
12
|
+
map(entity)[:subjective]
|
13
|
+
end
|
14
|
+
alias they subjective
|
15
|
+
alias he subjective
|
16
|
+
alias she subjective
|
17
|
+
|
18
|
+
# @param entity [#person, #plural?, #gender]
|
19
|
+
# @return [String]
|
20
|
+
def subjective_(entity)
|
21
|
+
subjective(entity).cap_first
|
22
|
+
end
|
23
|
+
alias they_ subjective_
|
24
|
+
alias he_ subjective_
|
25
|
+
alias she_ subjective_
|
26
|
+
|
27
|
+
# @param entity [#person, #plural?, #gender]
|
28
|
+
# @return [String]
|
29
|
+
def objective(entity)
|
30
|
+
map(entity)[:objective]
|
31
|
+
end
|
32
|
+
alias them objective
|
33
|
+
|
34
|
+
# @param entity [#person, #plural?, #gender]
|
35
|
+
# @return [String]
|
36
|
+
def objective_(entity)
|
37
|
+
objective(entity).cap_first
|
38
|
+
end
|
39
|
+
alias them_ objective_
|
40
|
+
|
41
|
+
# @param entity [#person, #plural?, #gender]
|
42
|
+
# @return [String]
|
43
|
+
def possessive(entity)
|
44
|
+
map(entity)[:possessive]
|
45
|
+
end
|
46
|
+
alias their possessive
|
47
|
+
|
48
|
+
# @param entity [#person, #plural?, #gender]
|
49
|
+
# @return [String]
|
50
|
+
def possessive_(entity)
|
51
|
+
possessive(entity).cap_first
|
52
|
+
end
|
53
|
+
alias their_ possessive_
|
54
|
+
|
55
|
+
# @param entity [#person, #plural?, #gender]
|
56
|
+
# @return [String]
|
57
|
+
def reflexive(entity)
|
58
|
+
map(entity)[:reflexive]
|
59
|
+
end
|
60
|
+
alias themselves reflexive
|
61
|
+
alias themself reflexive
|
62
|
+
|
63
|
+
# @param entity [#person, #plural?, #gender]
|
64
|
+
# @return [String]
|
65
|
+
def reflexive_(entity)
|
66
|
+
reflexive(entity).cap_first
|
67
|
+
end
|
68
|
+
alias themselves_ reflexive_
|
69
|
+
alias themself_ reflexive_
|
70
|
+
|
71
|
+
# @param entity [#person, #plural?, #gender]
|
72
|
+
# @return [Hash]
|
73
|
+
def map(entity)
|
74
|
+
plurality = (entity.plural? ? :plural : :singular)
|
75
|
+
maps[[(entity.person || 3), plurality, entity.gender]] ||
|
76
|
+
maps[[(entity.person || 3), plurality]]
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def maps
|
82
|
+
@maps ||= {
|
83
|
+
[1, :singular] => Hash[map_keys.zip(%w[I me my myself])],
|
84
|
+
[2, :singular] => Hash[map_keys.zip(%w[you you your yourself])],
|
85
|
+
[3, :singular] => Hash[map_keys.zip(%w[it it its itself])],
|
86
|
+
[3, :singular, :male] => Hash[map_keys.zip(%w[he him his himself])],
|
87
|
+
[3, :singular, :female] => Hash[map_keys.zip(%w[she her her herself])],
|
88
|
+
[3, :singular, :other] => Hash[map_keys.zip(%w[they them their themselves])],
|
89
|
+
[3, :singular, :neutral] => Hash[map_keys.zip(%w[it it its itself])],
|
90
|
+
[1, :plural] => Hash[map_keys.zip(%w[we us our ourselves])],
|
91
|
+
[2, :plural] => Hash[map_keys.zip(%w[you you your yourselves])],
|
92
|
+
[3, :plural] => Hash[map_keys.zip(%w[they them their themselves])]
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
def map_keys
|
97
|
+
@map_keys ||= %i[subjective objective possessive reflexive]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# @gamefic.script standard/lockable
|
2
|
+
|
3
|
+
require 'gamefic-standard/openable'
|
4
|
+
|
5
|
+
# A module for entities that are both openable and lockable.
|
6
|
+
#
|
7
|
+
module Lockable
|
8
|
+
include Openable
|
9
|
+
|
10
|
+
attr_accessor :lock_key
|
11
|
+
|
12
|
+
def locked=(bool)
|
13
|
+
@locked = bool
|
14
|
+
if @locked == true
|
15
|
+
self.open = false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def open=(bool)
|
20
|
+
@open = bool
|
21
|
+
@locked = false if @open == true
|
22
|
+
end
|
23
|
+
|
24
|
+
def locked?
|
25
|
+
@locked ||= false
|
26
|
+
end
|
27
|
+
|
28
|
+
def has_lock_key?
|
29
|
+
!@lock_key.nil?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Gamefic.script do
|
34
|
+
respond :lock, Use.available do |actor, thing|
|
35
|
+
actor.tell "You can't lock #{the thing}."
|
36
|
+
end
|
37
|
+
|
38
|
+
respond :_toggle_lock, Use.available(Lockable, :has_lock_key?) do |actor, thing|
|
39
|
+
verb = thing.locked? ? 'unlock' : 'lock'
|
40
|
+
key = nil
|
41
|
+
if thing.lock_key.parent == actor
|
42
|
+
key = thing.lock_key
|
43
|
+
end
|
44
|
+
if key.nil?
|
45
|
+
actor.tell "You don't have any way to #{verb} #{the thing}."
|
46
|
+
else
|
47
|
+
actor.tell "You #{verb} #{the thing} with #{the key}."
|
48
|
+
thing.locked = !thing.locked?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
respond :lock, Use.available(Lockable, :has_lock_key?), Use.children do |actor, thing, key|
|
53
|
+
if thing.lock_key == key
|
54
|
+
actor.perform :_toggle_lock, thing
|
55
|
+
else
|
56
|
+
actor.tell "You can't unlock #{the thing} with #{the key}."
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
respond :lock, Use.available(Lockable, :has_lock_key?), Use.available do |actor, thing, key|
|
61
|
+
actor.perform :take, key if key.parent != actor
|
62
|
+
actor.proceed if key.parent == actor
|
63
|
+
end
|
64
|
+
|
65
|
+
respond :unlock, Use.available do |actor, thing|
|
66
|
+
actor.tell "You can't unlock #{the thing}."
|
67
|
+
end
|
68
|
+
|
69
|
+
respond :unlock, Use.available(Lockable, :has_lock_key?), Use.children do |actor, thing, key|
|
70
|
+
if thing.lock_key == key
|
71
|
+
actor.perform :_toggle_lock, thing
|
72
|
+
else
|
73
|
+
actor.tell "You can't unlock #{the thing} with #{the key}."
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
respond :unlock, Use.available(Lockable, :has_lock_key?), Use.available do |actor, thing, key|
|
78
|
+
actor.perform :take, key if key.parent != actor
|
79
|
+
actor.proceed if key.parent == actor
|
80
|
+
end
|
81
|
+
|
82
|
+
respond :open, Use.available(Lockable) do |actor, thing|
|
83
|
+
if thing.locked?
|
84
|
+
actor.tell "#{The thing} is locked."
|
85
|
+
else
|
86
|
+
actor.proceed
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
interpret "lock :container with :key", "lock :container :key"
|
91
|
+
interpret "unlock :container with :key", "unlock :container :key"
|
92
|
+
interpret "open :container with :key", "unlock :container :key"
|
93
|
+
end
|