Module: StoryTeller::InstanceMethods

Included in:
Runtime
Defined in:
lib/story_teller/runtime.rb

Overview

module InstanceMethods

Constant Summary collapse

UndefinedMainPattern =
%r{undefined method 'Main' for an instance of Object}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#main_objectObject

Returns the value of attribute main_object.



30
31
32
# File 'lib/story_teller/runtime.rb', line 30

def main_object
  @main_object
end

#sessionObject

Returns the value of attribute session.



30
31
32
# File 'lib/story_teller/runtime.rb', line 30

def session
  @session
end

Instance Method Details

#apply_invocation_privileges(subject) ⇒ Object



207
208
209
210
# File 'lib/story_teller/runtime.rb', line 207

def apply_invocation_privileges(subject)
  StoryTeller::PrivilegeGrants.grant_session(subject, :admin) if @options[:admin]
  StoryTeller::PrivilegeGrants.grant_session(subject, :builder) if @options[:builder]
end

#apply_privileges(klass, privileges: StoryTeller::Privileges) ⇒ Object



212
213
214
215
216
# File 'lib/story_teller/runtime.rb', line 212

def apply_privileges(klass, privileges: StoryTeller::Privileges)
  return if klass.nil? || klass < privileges

  klass.include(privileges)
end

#bind_session(inflib = inform_library) ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/story_teller/runtime.rb', line 218

def bind_session(inflib = inform_library)
  @session ||= StoryTeller::IO::Session.new(
    machine: inflib,
    player: inflib.selfobj,
    state: :playing,
    settings: default_config
  ).tap do |session|
    apply_invocation_privileges(session)
  end

  @session.expose_to(inflib)
  @session
end

#canonical_location_candidateObject

rubocop: disable Metrics/CyclomaticComplexity



148
149
150
151
152
# File 'lib/story_teller/runtime.rb', line 148

def canonical_location_candidate
  inform_library&.location ||
    inform_library&.player&.location ||
    inform_library&.player&.spawn_point
end

#config_file_pathObject



38
39
40
# File 'lib/story_teller/runtime.rb', line 38

def config_file_path
  @config_file_path ||= game.config_file_path
end

#configure!(options) ⇒ Object



78
79
80
81
82
83
# File 'lib/story_teller/runtime.rb', line 78

def configure!(options)
  @options.merge!(options)
  reset_config_cache
  @game = StoryTeller::Game.new(@options)
  self
end

#configure_outputObject



241
242
243
244
245
# File 'lib/story_teller/runtime.rb', line 241

def configure_output
  return unless @options[:curses]

  StoryTeller::IO.default_output = StoryTeller::CursesAdapter.new
end

#default_configObject



32
33
34
35
36
# File 'lib/story_teller/runtime.rb', line 32

def default_config
  @default_config ||= StoryTeller::Config.defaults.merge(@options).tap do |config|
    config[:word_wrap] ||= StoryTeller::Terminal.word_wrap
  end
end

#ensuring_player_location(&block) ⇒ Object

TODO: Ensuring the location of the player object requires that this method be invoked before the move @player, @location operation in the InformLibrary#play method. This means that ensuring the location must take place either in the game- defined Initialise() method, or else it can happen in a provided LibraryExtension method, but in that case it would be at risk of being overridden by a game-defined Initialise() method. TODO: Figure out what the best approach here is. I think that if a multi-player game is to supply an entrypoint module, it will have to handle the location ensurance itself.



127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/story_teller/runtime.rb', line 127

def ensuring_player_location(&block)
  if persist?
    install_persisted_startup_location_hook
    persisted_location = StoryTeller::Engine.player_object&.location
    StoryTeller::PersistedStartupLocation.with(persisted_location) { block.call }
  else
    block.call
    location = location_candidate
    raise "InformLibrary location may not be left unset" if location.nil?

    inform_library&.PlayerTo(location, 1)
  end
end

#flush_session_outputObject



256
257
258
# File 'lib/story_teller/runtime.rb', line 256

def flush_session_output
  write_output(session.drain_output)
end

#game_componentsObject



58
59
60
# File 'lib/story_teller/runtime.rb', line 58

def game_components
  @game_components ||= @options.fetch(:game_components, '').split.map(&:to_sym)
end

#game_dir_nameObject



54
55
56
# File 'lib/story_teller/runtime.rb', line 54

def game_dir_name
  @game_dir_name ||= @options[:game_dir_name]
end

#game_file_pathObject



50
51
52
# File 'lib/story_teller/runtime.rb', line 50

def game_file_path
  @game_file_path ||= game.file_path
end

#game_pathObject



42
43
44
# File 'lib/story_teller/runtime.rb', line 42

def game_path
  @game_path ||= game.path
end

#grammar_module_pathObject



46
47
48
# File 'lib/story_teller/runtime.rb', line 46

def grammar_module_path
  @grammar_module_path ||= File.join(game_path, @options[:game_grammar_module_name])
end

#identityObject



186
187
188
# File 'lib/story_teller/runtime.rb', line 186

def identity
  self.object_id
end

#inform_libraryObject



182
183
184
# File 'lib/story_teller/runtime.rb', line 182

def inform_library
  @inform_library ||= StoryTeller::Engine.library
end

#inspectObject



334
335
336
# File 'lib/story_teller/runtime.rb', line 334

def inspect
  to_s
end

#install_persisted_startup_location_hookObject



106
107
108
109
110
# File 'lib/story_teller/runtime.rb', line 106

def install_persisted_startup_location_hook
  return if InformLibrary < StoryTeller::PersistedStartupMove

  InformLibrary.prepend(StoryTeller::PersistedStartupMove)
end

#invocation_contextObject



74
75
76
# File 'lib/story_teller/runtime.rb', line 74

def invocation_context
  @invocation_context ||= Struct.new(*invocation_properties)
end

#invocation_identityObject



190
191
192
# File 'lib/story_teller/runtime.rb', line 190

def invocation_identity
  @invocation_identity ||= StoryTeller::InvocationIdentity.new(self)
end

#invocation_propertiesObject



70
71
72
# File 'lib/story_teller/runtime.rb', line 70

def invocation_properties
  @invocation_properties ||= @options.fetch(:properties, '').split.map(&:to_sym)
end

#invoke_mainObject



95
96
97
98
99
100
101
102
103
104
# File 'lib/story_teller/runtime.rb', line 95

def invoke_main
  ::Object.new.send(:Main)
rescue NameError => e
  if UndefinedMainPattern.match?(e.message)
    log.fatal "Main() definition is missing"
  else
    log.fatal "Fatal error invoking Main(): #{e.message}"
  end
  abort 'Terminating'
end

#location_candidateObject



141
142
143
144
145
# File 'lib/story_teller/runtime.rb', line 141

def location_candidate
  return persisted_location_candidate if persist?

  canonical_location_candidate
end

#manage_privilegesObject



198
199
200
201
202
203
204
205
# File 'lib/story_teller/runtime.rb', line 198

def manage_privileges
  apply_privileges(self.class, privileges: StoryTeller::Privileges)
  apply_privileges(InformLibrary, privileges: StoryTeller::Privileges) if defined?(InformLibrary)
  apply_privileges(StoryTeller::IO::Session, privileges: StoryTeller::Privileges)

  StoryTeller::PrivilegeGrants.mode = @options.fetch(:privilege_mode, :session_only)
  apply_invocation_privileges(self)
end

#persist?Boolean

Returns:

  • (Boolean)


112
113
114
# File 'lib/story_teller/runtime.rb', line 112

def persist?
  @options[:persist]
end

#persisted_location_candidateObject



154
155
156
157
158
# File 'lib/story_teller/runtime.rb', line 154

def persisted_location_candidate
  inform_library&.player&.location ||
    inform_library&.location ||
    inform_library&.player&.spawn_point
end

#persistenceObject



232
233
234
# File 'lib/story_teller/runtime.rb', line 232

def persistence
  StoryTeller::Persistence.instance
end

#play_gameObject



270
271
272
273
274
275
276
277
278
# File 'lib/story_teller/runtime.rb', line 270

def play_game
  log.trace "#{self}#play_game"
  configure_output
  use_persistent_selfobj
  bind_session
  ensuring_player_location { invoke_main }
  flush_session_output
  read_eval_print_loop
end

#privileged_identities(privilege) ⇒ Object



194
195
196
# File 'lib/story_teller/runtime.rb', line 194

def privileged_identities(privilege)
  [invocation_identity].select { |identity| identity.privileged?(privilege) }
end

#project_dir_pathObject



62
63
64
65
66
67
68
# File 'lib/story_teller/runtime.rb', line 62

def project_dir_path
  @project_dir_path ||= begin
    story_dir_path = File.expand_path(__dir__)
    lib_dir_path = File.expand_path(File.dirname(story_dir_path))
    File.expand_path(File.dirname(lib_dir_path))
  end
end

#quit_gameObject



280
281
282
283
284
285
286
# File 'lib/story_teller/runtime.rb', line 280

def quit_game
  write_output "[Hit enter to exit.]"
  $stdin.getc
  # Curses.getch
  # Curses.close_screen
  exit
end

#readObject



236
237
238
239
# File 'lib/story_teller/runtime.rb', line 236

def read
  flush_session_output unless session.nil?
  $stdin.gets(chomp: true)
end

#read_eval_print_loopObject



260
261
262
263
264
265
266
267
268
# File 'lib/story_teller/runtime.rb', line 260

def read_eval_print_loop
  loop do
    prompt unless session.state == :more
    flush_session_output
    write_output(session.process(read, inform_library))
  end
rescue Interrupt => e
  write_output("\n#{e.class.name}\n")
end

#reset_config_cacheObject



85
86
87
88
89
90
91
92
93
# File 'lib/story_teller/runtime.rb', line 85

def reset_config_cache
  @config_file_path = nil
  @default_config = nil
  @game_components = nil
  @game_dir_name = nil
  @game_file_path = nil
  @game_path = nil
  @grammar_module_path = nil
end

#restart_gameObject



288
289
290
# File 'lib/story_teller/runtime.rb', line 288

def restart_game
  L__M(:Restart, 2)
end

#restore_game(callback = nil) ⇒ Object

rubocop: disable Metrics/AbcSize rubocop: disable Metrics/MethodLength



314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/story_teller/runtime.rb', line 314

def restore_game(callback = nil)
  filename = format('%s.sav', game.name)
  files = Dir.glob(File.join(format('%s_*.sav', game.name)))
  filename = files.max unless files.empty?
  println "Enter a file name."
  print "Default is \"#{filename}\": "
  flush_session_output
  response = $stdin.gets&.strip.to_s
  filename = response unless response.empty?
  StoryTeller::Snapshots.import_from_file(filename)
  restore_player_location
  inform_library.println(callback&.call)
end

#restore_player_locationObject

rubocop: enable Metrics/CyclomaticComplexity



161
162
163
164
165
166
# File 'lib/story_teller/runtime.rb', line 161

def restore_player_location
  inform_library.PlayerTo(
    inform_library.player.location,
    -1 # Quietly
  )
end

#save_game(callback = nil) ⇒ Object

rubocop: disable Metrics/AbcSize rubocop: disable Metrics/MethodLength



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/story_teller/runtime.rb', line 294

def save_game(callback = nil)
  timestamp = Time.now.utc.strftime('%Y-%m-%d_%H%M%S')
  filename = format(
    '%<game_name>s_%<timestamp>s.sav',
    game_name: game.name,
    timestamp: timestamp
  )
  println "Enter a file name."
  print "Default is \"#{filename}\": "
  flush_session_output
  response = $stdin.gets&.strip.to_s
  filename = response unless response.empty?
  StoryTeller::Snapshots.export_to_file(filename)
  inform_library.println(callback&.call)
end

#to_sObject

rubocop: enable Metrics/AbcSize rubocop: enable Metrics/MethodLength



330
331
332
# File 'lib/story_teller/runtime.rb', line 330

def to_s
  "#<#{self.class.name}:#{object_id}>"
end

#use_persistent_selfobjObject

Initially, inform_library defaults to using Inform::Parser::SelfObj. However, that SelfObj is by default an instance of an Inform::Ephemeral::Object. Of course, persistence is not really required for a play session of a single player story game, since the snapshot file save feature is implemented. Nevertheless, it is the intention of this application to demonstrate persistence of Inform-esque data into a PostgreSQL database. And so the default selfobj is overridden here with a non-ephemeral version.



176
177
178
179
180
# File 'lib/story_teller/runtime.rb', line 176

def use_persistent_selfobj
  require_relative 'player_character'

  StoryTeller::Engine.player_object = StoryTeller::PlayerCharacter("(self object)")
end

#write_output(value) ⇒ Object



247
248
249
250
251
252
253
254
# File 'lib/story_teller/runtime.rb', line 247

def write_output(value)
  return if value.nil? || value.empty?

  output = StoryTeller::IO.default_output || $stdout

  output.write(value)
  $stdout.flush if output.equal?($stdout)
end