ifrb 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f165e1c66438091ed255557f44eed72ca9eaf219
4
+ data.tar.gz: 6f433a81afa77053b2b054b85bc166f42e8496b8
5
+ SHA512:
6
+ metadata.gz: 8d727149c338ec392fb376fba3fdef7ad32202df0506b30852e15e654b27e90cb73146423a0c314ecf0a22e59e2c7feb05d04850e46ed29b82a0a5a4550aba49
7
+ data.tar.gz: 9941d5b139b9c3e09a4927518de2251b6bbf41459d310cbda65293d8b86797894703beb8bd59ce096995085158256eb46e6e1a70b73778d78a79b726fc292323
@@ -0,0 +1,74 @@
1
+ # ifrb
2
+
3
+ Interactive fiction (IF), for interactive Ruby (IRB). Play interactive fiction games from within the cozy familiarity of irb! Write stories that incorporate actual, executable Ruby code in the dialog!
4
+
5
+ ## Installation
6
+
7
+ RubyGems is your friend:
8
+
9
+ $ gem install ifrb
10
+
11
+ ## Usage
12
+
13
+ Try it with one of the examples:
14
+
15
+ $ ifrb basil
16
+
17
+ Or, if you want to do it from within IRB directly:
18
+
19
+ $ irb
20
+ irb> require 'ifrb'
21
+ irb> ifrb "basil"
22
+
23
+ ## Writing Your Own Adventures
24
+
25
+ Just create a new ruby file. You need at least one method, called `start`, which will be called when your adventure loads.
26
+
27
+ def start
28
+ # ...
29
+ end
30
+
31
+ Define methods inside that method to add actions that are available for players. At the very least, you should add a method called `look`, which will be called when the room loads. The `look` method should describe the room:
32
+
33
+ def start
34
+ def look
35
+ _format <<-DESC
36
+ You see a big room. Not much else in here.
37
+ DESC
38
+ end
39
+
40
+ # ...
41
+ end
42
+
43
+ Other rooms are defined as top-level methods:
44
+
45
+ def start
46
+ #...
47
+ end
48
+
49
+ def monster
50
+ def look
51
+ _format "Yuck! A monster!"
52
+ end
53
+ # ...
54
+ end
55
+
56
+ Transition to rooms using the `_load_room` method:
57
+
58
+ def start
59
+ def north
60
+ _load_room :monster
61
+ end
62
+ end
63
+
64
+ def monster
65
+ # ...
66
+ end
67
+
68
+ See the examples for more information about writing your own adventures. Good luck!
69
+
70
+ ## License
71
+
72
+ Creative Commons Attribution 4.0 International License -- (c) Jamis Buck <jamis@jamisbuck.org>
73
+
74
+ <a rel="license" href="http://creativecommons.org/licenses/by/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by/4.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" property="dct:title">IFRB</span> by <a xmlns:cc="http://creativecommons.org/ns#" href="http://github.com/jamis/ifrb" property="cc:attributionName" rel="cc:attributionURL">Jamis Buck</a> is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 International License</a>.<br />Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://github.com/jamis/ifrb" rel="dct:source">http://github.com/jamis/ifrb</a>.
@@ -0,0 +1,7 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,29 @@
1
+ #!/bin/sh ruby
2
+
3
+ require "irb"
4
+
5
+ game = ARGV.pop or abort "please specify the game you'd like to play"
6
+ game = game.gsub(/\.rb$/, "")
7
+
8
+ if !File.exists?("#{game}.rb")
9
+ ex_path = File.join(File.dirname(__FILE__), "..", "examples", game)
10
+ if File.exists?("#{ex_path}.rb")
11
+ game = ex_path
12
+ else
13
+ abort "no such game `#{game}'"
14
+ end
15
+ end
16
+
17
+ old_setup = IRB.method(:setup)
18
+ IRB.singleton_class.send(:define_method, :setup) do |*args|
19
+ old_setup.call(*args)
20
+ old_rc = IRB.conf[:IRB_RC]
21
+ IRB.conf[:IRB_RC] = ->(context) do
22
+ old_rc[context] if old_rc
23
+ command = "ifrb #{game.inspect}\n"
24
+ command.reverse.chars.each { |c| context.irb.scanner.ungetc(c) }
25
+ end
26
+ end
27
+
28
+ ARGV.push "-rifrb"
29
+ IRB.start(__FILE__)
@@ -0,0 +1,305 @@
1
+ WORDS = %w(wonky insipid squeak babushka cantilever intrepid flipper
2
+ periwinkle ewok munch gorgonzola ogle)
3
+
4
+ def start
5
+ _format <<-DESC
6
+ "Confound that blasted..."
7
+
8
+ The dust swirles and settles to the gentle swearing of your master, Basil Smockwhitener (wizard and gentleman). Coughing, you push your way through to him, and find him trapped beneath a large fallen beam.
9
+
10
+ Try as you might, thought, it's too much for you to lift. Basil appears to be injured, but he shoos you away.
11
+
12
+ "Stop, Fabian," he says. "Just--stop. It's too heavy. Go on ahead and find the Magic Doohickie. We can use it to get this beam off me. It's the only way, now. No arguing! Just go!"
13
+
14
+ You shrug, recognizing that tone of voice, and resigned as usual. "Yes, sir. I'll see what I can find."
15
+
16
+ DESC
17
+
18
+ armor = Class.new { def ticklish?; false; end }
19
+
20
+ doohickie = Class.new do
21
+ attr_reader :magic_a, :magic_b, :magic_c
22
+
23
+ def initialize(a, b, c)
24
+ @magic_a = a
25
+ @magic_b = b
26
+ @magic_c = c
27
+ end
28
+
29
+ def ==(h)
30
+ magic_a == h.magic_a &&
31
+ magic_b == h.magic_b &&
32
+ magic_c == h.magic_c
33
+ end
34
+
35
+ def dup
36
+ puts "Yeah, you kinda wish it were that easy, eh?"
37
+ nil
38
+ end
39
+
40
+ alias clone dup
41
+ end
42
+
43
+ @__basil_freed = false
44
+ @__locked = true
45
+ @__armor = armor.new
46
+ @__armory_blocked = true
47
+ @__looted = false
48
+ @__doohickie = doohickie.new(rand(100), rand(100), rand(100))
49
+ @__setting_goal = rand(100)+1
50
+
51
+ undef start
52
+ basil
53
+ end
54
+
55
+ def basil
56
+ def look
57
+ if @__basil_freed
58
+ _format <<-DESC
59
+ Basil is free! You've done it! He's limping a bit, but he insists that it's nothing that a few cookies and a good night's rest won't cure. He gestures anxious to the south. "Let's go, Fabian!"
60
+ DESC
61
+ elsif @__looted
62
+ _format <<-DESC
63
+ You show Basil the Magic Doohickie and he breathes a sigh of relief. "You've done it, Fabian! Jolly good, old man! Now, just [adjust] the thing to the correct setting and get this beam off me. What? How should I know what the correct setting is? Just experiment. It's something between 1 and 100."
64
+ DESC
65
+ else
66
+ _format <<-DESC
67
+ What was formerly the ceiling now lies mostly on the floor, much of it unfortunately on your master, Basil. He gestures angrily at you to get moving. "Find the Doohickie!"
68
+
69
+ The passage goes south (to the exit), and north (towards the treasure chamber). Presumably, Basil wants you to go north.
70
+ DESC
71
+ end
72
+ end
73
+
74
+ def adjust(setting=0)
75
+ if !@__basil_freed && @__looted
76
+ if setting < @__setting_goal
77
+ _format "The doohickie grows cooler momentarily."
78
+ elsif setting > @__setting_goal
79
+ _format "The doohickie grows warmer momentarily."
80
+ else
81
+ @__basil_freed = true
82
+ _format <<-DESC
83
+ With a satisfying *thunk*, the beam that had trapped Basil disappears! He stands up, dusts himself off, and shakes your hand a bit more vigorously than you think the situation calls for. "Well done, man! You did it! Now, let's get out of here. To the south!"
84
+ DESC
85
+ end
86
+ else
87
+ _format "Adjust what? You're funny."
88
+ end
89
+ end
90
+
91
+ def south
92
+ if @__basil_freed
93
+ _load_room :escape
94
+ else
95
+ _format <<-DESC
96
+ Basil shouts angrily at you. "Not that way, you oaf! North! North! Get the Doohickie!" His mustaches are practically bristling.
97
+ DESC
98
+ end
99
+ end
100
+
101
+ def north
102
+ if @__basil_freed
103
+ _format <<-DESC
104
+ "What are you, a glutton for punishment?" Basil shakes his head in disbelief. "Come along, Fabian. South, man! Let's get out of here."
105
+ DESC
106
+ else
107
+ _load_room :lock
108
+ end
109
+ end
110
+ end
111
+
112
+ def lock
113
+ def look
114
+ if @__locked
115
+ _format <<-DESC
116
+ Not that you have much experience with looting abandoned treasure chambers or anything, but the pattern on the far wall looks a lot like a magical lock. Your hunch is borne out by a large sign above it that reads:
117
+
118
+ "This is a magical lock. You'll never figure it out. Only a real magician, one able to [invoke] a spell that takes a string and reverses it, will ever manage to unlock it."
119
+
120
+ Better get busy. The alternative is to go south again and tell Basil you failed. (Whew. That gives you chills just thinking about it.)
121
+ DESC
122
+ else
123
+ _format <<-DESC
124
+ There is a passage going north. This is because you successfully invoked the spell that unlocked it. Good for you! Basil would be so proud. Assuming he noticed.
125
+
126
+ Oh, speaking of that. You can also go south, back to Basil.
127
+ DESC
128
+ end
129
+ end
130
+
131
+ def south
132
+ _load_room :basil
133
+ end
134
+
135
+ def north
136
+ if @__locked
137
+ _format <<-DESC
138
+ What, through a magically locked door? You might try opening it first. Although, you'll need to unlock it before you can open it. Have you tried [invoke] yet?
139
+ DESC
140
+ else
141
+ _load_room :armory
142
+ end
143
+ end
144
+
145
+ def invoke(spell=nil, &block)
146
+ if spell.nil? && !block
147
+ _format <<-DESC
148
+ Nothing happens, and it does so in a surprisingly uninspiring way. (Maybe you should provide the spell you want to cast? Perhaps as a lambda, or a block? But I'm just the narrator. What would I know about magic?)
149
+ DESC
150
+ elsif spell && !spell.respond_to?(:call)
151
+ _format "Um. I'm no expert, mind, but that doesn't look like a spell."
152
+ else
153
+ spell ||= block
154
+ words = WORDS.shuffle[0,5]
155
+
156
+ _format "Right. Let's see if this cantrip works. I'll just plug in a few words here..."
157
+ success = words.all? do |word|
158
+ result = spell[word]
159
+ puts " - `#{word}' becomes `#{result}'..."
160
+ word.reverse == result
161
+ end
162
+
163
+ if success
164
+ @__locked = false
165
+ _format "What the..?! You did it! A passage opens to the north!"
166
+ else
167
+ _format "Hmm. That's not quite right. Maybe you should have another go?"
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ def armory
174
+ def look
175
+ if @__armory_blocked
176
+ _format <<-DESC
177
+ The walls of this room are covered with exotic (and ancient-looking) weapons. Suits of armor are arranged haphazardly on stands, with one particularly imposing specimen standing--eerily life-like--right in front a door to the north. On its tabard is written the words:
178
+
179
+ "Tickle me. I dare you."
180
+
181
+ The only other exit is to the south.
182
+ DESC
183
+ else
184
+ _format <<-DESC
185
+ Aside from all the weapons and armor arranged around the walls, a large suit of armor lies prone on the floor.
186
+
187
+ One passage lies open to the north, and another to the south.
188
+ DESC
189
+ end
190
+ end
191
+
192
+ def south
193
+ _load_room :lock
194
+ end
195
+
196
+ def north
197
+ if @__armory_blocked
198
+ _format <<-DESC
199
+ You think so, do you? Even with that big suit of armor daring you to tickle it?
200
+ DESC
201
+ else
202
+ _load_room :treasury
203
+ end
204
+ end
205
+
206
+ def tickle(what=:armor)
207
+ if what == :armor
208
+ if @__armor.ticklish?
209
+ @__armory_blocked = false
210
+
211
+ def tickle(what=:armor)
212
+ _format "No need for more tickling. The armor is defunct!"
213
+ end
214
+
215
+ _format <<-DESC
216
+ You boldly start scratching the suit along its sides, viciously making baby noises while watching the suit's arms. Hollowly, from deep within the armor, a tinny-sounding giggling begins. It grows in volume like the Doppler effect with poor AM reception until the suit's arms start flailing helplessly and the armor falls to the floor.
217
+
218
+ As you back away, the giggling slowly fades, leaving nothing but rather unsettling echoes.
219
+
220
+ The passage to the north is unblocked!
221
+ DESC
222
+ else
223
+ _format <<-DESC
224
+ You reach out hesitantly and poke the suit of armor about where its belly button ought to be. Feeling faintly ridiculous, you add, "goochie goochie goo."
225
+
226
+ One of the suit's arms suddenly holds up a sign that reads, "Sorry. #ticklish? == false," before the other blindsides you and knocks you to the floor. By the time you stand up again, the armor has reset.
227
+
228
+ Hmm. Maybe you ought to try to [enhance] the armor with a module that makes it [ticklish?]
229
+ DESC
230
+ end
231
+ else
232
+ _format "That's a rather odd thing to tickle."
233
+ end
234
+ end
235
+
236
+ def enhance(mod)
237
+ if @__armory_blocked
238
+ @__armor.extend(mod)
239
+ _format "Your enhancement settles experimentally onto the armor. Maybe try tickling it now?"
240
+ else
241
+ _format "The armor is nonfunctional, now. Enhancing it won't do anything."
242
+ end
243
+ end
244
+ end
245
+
246
+ def treasury
247
+ def look
248
+ if @__looted
249
+ _format <<-DESC
250
+ The fake Magic Doohickie looks pretty convincing there on its pedestal. Perhaps you should go rescue Basil now that yo uhave the real one. Go south!
251
+ DESC
252
+ else
253
+ _format <<-DESC
254
+ This is it! The treasury, containing the ancient and mysterious Magic Doohickie of Hank Doolickie! Its rumored powers are many, and its rumored guardians are...well...many, also. Somehow, you need to [swap] it with an identical [doohickie], so that the guards won't realize it's been taken.
255
+
256
+ Or, you can give up and go south. Quitter.
257
+ DESC
258
+ end
259
+ end
260
+
261
+ def doohickie
262
+ @__doohickie
263
+ end
264
+
265
+ def south
266
+ _load_room :armory
267
+ end
268
+
269
+ def swap(a, b)
270
+ if @__looted
271
+ _format "Wait, don't do that! You've already pulled off the heist!"
272
+ elsif a.equal?(b)
273
+ _format "What are you trying to pull? You can't swap a thing with itself!"
274
+ else
275
+ a, b = b, a if b == @__doohickie
276
+ if a == @__doohickie
277
+ if a == b
278
+ @__looted = true
279
+ @__my_doohickie, @__doohickie = a, b
280
+ _format <<-DESC
281
+ Whoa! That was tricky! You totally swapped your fake for the real deal! This means...wait. This means you can go free Basil, now! Quickly, head south!
282
+ DESC
283
+ else
284
+ _format <<-DESC
285
+ Hmm...those don't quite look equivalent. I suspect something really, really, really, really, really, really, really bad would happen if you actually tried swapping them. Can you make your fake one look anything more like the real [doohickie]?
286
+ DESC
287
+ end
288
+ else
289
+ _format "That's...odd. Try that again, this time with the [doohickie]."
290
+ end
291
+ end
292
+ end
293
+ end
294
+
295
+ def escape
296
+ def look
297
+ _format <<-DESC
298
+ You've done it! You recovered the Magic Doohickie, rescued Basil, and capped it all of with a daring escape. Well, maybe not *daring*. But an escape, nonetheless.
299
+
300
+ Well done!
301
+ DESC
302
+
303
+ quit
304
+ end
305
+ end
@@ -0,0 +1,66 @@
1
+ if !defined? IRB
2
+ abort "ifrb will only work within an IRB session"
3
+ end
4
+
5
+ IRB.conf[:PROMPT][:IFRB] = {
6
+ :PROMPT_I => "%N> ", # "initial" prompt
7
+ :PROMPT_C => "..", # continued command
8
+ :PROMPT_S => "..", # continued string
9
+ :PROMPT_N => "..", # nested command
10
+ :RETURN => "" # formatting for return values
11
+ }
12
+
13
+ def ifrb(game)
14
+ context.prompt_mode = :IFRB
15
+
16
+ class <<self
17
+ def method_missing(sym, *args)
18
+ if args.empty?
19
+ sym
20
+ else
21
+ super
22
+ end
23
+ end
24
+ end
25
+
26
+ def _load_room(name)
27
+ context.irb_name = name
28
+
29
+ (methods - @_baseline).each do |method|
30
+ eval "undef #{method}"
31
+ end
32
+
33
+ send(name)
34
+
35
+ look if respond_to?(:look)
36
+ end
37
+
38
+ def _format(text, length:60, indent:2)
39
+ indent = " " * indent
40
+
41
+ text.each_line do |line|
42
+ line.strip!
43
+
44
+ if line.empty?
45
+ puts
46
+ else
47
+ while line.length > length
48
+ break_at = length
49
+ break_at -= 1 while break_at > 0 && line[break_at] != " "
50
+ break_at = length if break_at == 0
51
+ puts indent + line[0, break_at]
52
+ line = line[break_at+1..-1]
53
+ end
54
+
55
+ puts indent + line if line.length > 0
56
+ end
57
+ end
58
+
59
+ nil
60
+ end
61
+
62
+ load "#{game}.rb"
63
+ @_baseline = methods
64
+
65
+ _load_room :start
66
+ end
@@ -0,0 +1,9 @@
1
+ module IFRB
2
+ module Version
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join(".")
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ifrb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jamis Buck
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-17 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: We spend much of our professional lives in IRB. Why not make a game of
14
+ it?
15
+ email:
16
+ - jamis@jamisbuck.org
17
+ executables:
18
+ - ifrb
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - README.md
23
+ - Rakefile
24
+ - bin/ifrb
25
+ - examples/basil.rb
26
+ - lib/ifrb.rb
27
+ - lib/ifrb/version.rb
28
+ homepage: http://github.com/jamis/ifrb
29
+ licenses:
30
+ - CC4.0
31
+ metadata: {}
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ requirements: []
47
+ rubyforge_project:
48
+ rubygems_version: 2.4.5
49
+ signing_key:
50
+ specification_version: 4
51
+ summary: A system for running interactive fiction games within IRB.
52
+ test_files: []