gamefic 1.3.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/gamefic/scene.rb CHANGED
@@ -1,15 +1,16 @@
1
+ require 'gamefic/scene_data'
2
+
1
3
  module Gamefic
2
4
 
3
5
  module Scene
4
6
  autoload :Base, 'gamefic/scene/base'
5
7
  autoload :Custom, 'gamefic/scene/custom'
6
8
  autoload :Active, 'gamefic/scene/active'
7
- autoload :Passive, 'gamefic/scene/passive'
8
9
  autoload :Pause, 'gamefic/scene/pause'
9
10
  autoload :Conclusion, 'gamefic/scene/conclusion'
10
11
  autoload :MultipleChoice, 'gamefic/scene/multiple_choice'
12
+ autoload :MultipleScene, 'gamefic/scene/multiple_scene'
11
13
  autoload :YesOrNo, 'gamefic/scene/yes_or_no'
12
- autoload :Question, 'gamefic/scene/question'
13
14
  end
14
15
 
15
16
  end
@@ -5,13 +5,8 @@ module Gamefic
5
5
  # a Plot.
6
6
  #
7
7
  class Scene::Active < Scene::Base
8
- def start actor
9
- # TODO Anything necessary here?
10
- end
11
8
  def finish actor, input
12
- last_order = actor.perform input
13
- # HACK Set the last_order here so inline performs don't set it
14
- actor.send(:last_order=, last_order)
9
+ actor.perform input, from_user: true
15
10
  end
16
11
  end
17
12
 
@@ -1,21 +1,47 @@
1
- module Gamefic::Scene
1
+ module Gamefic
2
2
 
3
3
  # The Base Scene is not intended for instantiation. Other Scene classes
4
4
  # should inherit from it.
5
5
  #
6
- class Base
7
- def prompt
8
- @prompt ||= '>'
9
- end
6
+ class Scene::Base
10
7
  def start actor
11
-
8
+ start_data_for actor
12
9
  end
10
+
13
11
  def finish actor, input
14
-
12
+ finish_data_for actor, input
15
13
  end
16
- def state
14
+
15
+ def type
17
16
  self.class.to_s.split('::').last
18
17
  end
18
+
19
+ def data_class
20
+ SceneData::Base
21
+ end
22
+
23
+ # Get the prompt to be displayed to the user when accepting input.
24
+ #
25
+ # @return [String] The text to be displayed.
26
+ def prompt_for actor
27
+ character_data[actor].prompt || '>'
28
+ end
29
+
30
+ private
31
+
32
+ def character_data
33
+ @character_data ||= {}
34
+ end
35
+
36
+ def start_data_for actor
37
+ character_data[actor] = data_class.new
38
+ end
39
+
40
+ def finish_data_for actor, input
41
+ data = character_data[actor]
42
+ data.input = input.strip
43
+ data
44
+ end
19
45
  end
20
46
 
21
47
  end
@@ -3,9 +3,6 @@ module Gamefic
3
3
  # A Conclusion ends the Plot (or the character's participation in it).
4
4
  #
5
5
  class Scene::Conclusion < Scene::Custom
6
- def initialize &block
7
- @start = block
8
- end
9
6
  end
10
7
 
11
8
  end
@@ -1,21 +1,42 @@
1
1
  module Gamefic
2
2
 
3
3
  # A Custom Scene is a generic scene that allows for complete configuration
4
- # of its behavior upon instantiation. It is suitable for direct instantion
4
+ # of its behavior upon instantiation. It is suitable for direct instantiation
5
5
  # or extension by other Scene classes.
6
6
  #
7
7
  class Scene::Custom < Scene::Base
8
- def initialize config = {}
9
- @start = config[:start]
10
- @finish = config[:finish]
11
- @prompt = config[:prompt]
8
+ def initialize
9
+ yield self if block_given?
12
10
  end
11
+ def on_start &block
12
+ @start = block
13
+ end
14
+
15
+ def on_finish &block
16
+ @finish = block
17
+ end
18
+
13
19
  def start actor
14
- @start.call(actor) unless @start.nil?
20
+ data = start_data_for(actor)
21
+ do_start_block actor, data
22
+ data
15
23
  end
24
+
16
25
  def finish actor, input
17
- @finish.call(actor, input) unless @finish.nil?
26
+ data = finish_data_for(actor, input)
27
+ do_finish_block actor, data
28
+ end
29
+
30
+ private
31
+
32
+ def do_start_block actor, data
33
+ @start.call actor, data unless @start.nil?
34
+ end
35
+
36
+ def do_finish_block actor, data
37
+ @finish.call actor, data unless @finish.nil?
18
38
  end
39
+
19
40
  end
20
41
 
21
42
  end
@@ -8,59 +8,52 @@ module Gamefic
8
8
  # instead of a String.
9
9
  #
10
10
  class Scene::MultipleChoice < Scene::Custom
11
- autoload :Input, 'gamefic/scene/multiple_choice/input'
12
-
13
- def initialize config = {}
14
- super
15
- @options = (config[:options] || [])
16
- @invalid_choice_message = config[:invalid_choice_message]
11
+ def data_class
12
+ SceneData::MultipleChoice
17
13
  end
18
-
14
+
19
15
  def start actor
20
- @current_options = @options.clone
21
- @start.call(actor, @current_options) unless @start.nil?
22
- tell_choices actor
16
+ data = start_data_for(actor)
17
+ do_start_block actor, data
18
+ tell_options actor, data
23
19
  end
24
20
 
25
21
  def finish actor, input
26
- this_scene = actor.scene
27
- index = nil
28
- choice = nil
29
- if input.strip =~ /[0-9]+/ and input.to_i > 0
30
- index = input.to_i - 1
31
- choice = @current_options[index]
22
+ data = finish_data_for(actor, input)
23
+ get_choice data
24
+ if data.selection.nil?
25
+ actor.tell data.invalid_message
26
+ tell_options actor, data
27
+ else
28
+ do_finish_block actor, data
29
+ end
30
+ data
31
+ end
32
+
33
+ private
34
+
35
+ def get_choice data
36
+ if data.input.strip =~ /[0-9]+/ and data.input.to_i > 0
37
+ data.number = data.input.to_i
38
+ data.index = data.number - 1
39
+ data.selection = data.options[data.number - 1]
32
40
  else
33
41
  index = 0
34
- @current_options.each { |o|
35
- if o.casecmp(input).zero?
36
- choice = o
42
+ data.options.each { |o|
43
+ if o.casecmp(data.input).zero?
44
+ data.selection = o
45
+ data.index = index
46
+ data.number = index + 1
37
47
  break
38
48
  end
39
49
  index += 1
40
50
  }
41
51
  end
42
- if choice.nil?
43
- actor.tell invalid_choice_message
44
- tell_choices actor
45
- else
46
- input_object = Input.new(input, index, choice)
47
- super actor, input_object
48
- end
49
52
  end
50
53
 
51
- def prompt
52
- @prompt ||= "Enter a choice:"
53
- end
54
-
55
- def invalid_choice_message
56
- @invalid_choice_message ||= "That's not a valid selection."
57
- end
58
-
59
- private
60
-
61
- def tell_choices actor
54
+ def tell_options actor, data
62
55
  list = '<ol class="multiple_choice">'
63
- @current_options.each { |o|
56
+ data.options.each { |o|
64
57
  list += "<li>#{o}</li>"
65
58
  }
66
59
  list += "</ol>"
@@ -0,0 +1,15 @@
1
+ module Gamefic
2
+
3
+ class Scene::MultipleScene < Scene::Custom
4
+ def data_class
5
+ SceneData::MultipleScene
6
+ end
7
+
8
+ def finish actor, input
9
+ data = super
10
+ unless data.selection.nil?
11
+ actor.cue data.scene_for(selection)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,23 +1,12 @@
1
1
  module Gamefic
2
2
 
3
- # Wait for input. After the scene is finished (e.g., the player presses
4
- # Enter), the :active scene will be cued if no other scene has been prepared
5
- # or cued.
3
+ # Pause for user input.
6
4
  #
7
5
  class Scene::Pause < Scene::Custom
8
- def initialize prompt = nil, &block
9
- @prompt = prompt
10
- @start = block
11
- end
12
6
  def start actor
13
- @start_scene = actor.scene
14
- super
15
- end
16
- def finish actor, input
17
- actor.cue :active if (actor.scene == @start_scene and actor.next_scene.nil?)
18
- end
19
- def prompt
20
- @prompt ||= "Press Enter to continue..."
7
+ data = start_data_for(actor)
8
+ data.prompt = 'Press enter to continue...'
9
+ do_start_block actor, data
21
10
  end
22
11
  end
23
12
 
@@ -6,23 +6,18 @@ module Gamefic
6
6
  # other scene has been prepared or cued.
7
7
  #
8
8
  class Scene::YesOrNo < Scene::Custom
9
- def initialize prompt = nil, &block
10
- @prompt = prompt
11
- @finish = block
9
+ def data_class
10
+ SceneData::YesOrNo
12
11
  end
12
+
13
13
  def finish actor, input
14
- answer = nil
15
- if input.downcase[0, 1] == "y"
16
- answer = "yes"
17
- elsif input.downcase[0, 1] == "n"
18
- answer = "no"
19
- end
20
- if answer.nil?
21
- actor.tell "Please enter Yes or No."
22
- else
14
+ data = finish_data_for(actor, input)
15
+ if data.yes? or data.no?
23
16
  this_scene = actor.scene
24
- @finish.call actor, answer
17
+ do_finish_block actor, data
25
18
  actor.cue :active if (actor.scene == this_scene and actor.next_scene.nil?)
19
+ else
20
+ actor.tell data.invalid_message
26
21
  end
27
22
  end
28
23
  end
@@ -0,0 +1,9 @@
1
+ module Gamefic
2
+
3
+ module SceneData
4
+ autoload :Base, 'gamefic/scene_data/base'
5
+ autoload :MultipleChoice, 'gamefic/scene_data/multiple_choice'
6
+ autoload :YesOrNo, 'gamefic/scene_data/yes_or_no'
7
+ end
8
+
9
+ end
@@ -0,0 +1,12 @@
1
+ module Gamefic
2
+
3
+ class SceneData::Base
4
+ attr_writer :prompt
5
+ attr_accessor :input
6
+
7
+ def prompt
8
+ @prompt ||= '>'
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,19 @@
1
+ module Gamefic
2
+
3
+ class SceneData::MultipleChoice < SceneData::Base
4
+ attr_accessor :selection
5
+ attr_accessor :number
6
+ attr_accessor :index
7
+ attr_writer :invalid_message
8
+ def options
9
+ @options ||= []
10
+ end
11
+ def prompt
12
+ @prompt ||= 'Enter a choice:'
13
+ end
14
+ def invalid_message
15
+ @invalid_message ||= 'That is not a valid choice.'
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,25 @@
1
+ module Gamefic
2
+
3
+ class SceneData::MultipleScene < SceneData::MultipleChoice
4
+ attr_accessor :selection
5
+ attr_accessor :number
6
+ def options
7
+ scene_map.keys
8
+ end
9
+
10
+ def map choice, scene
11
+ scene_map[choice] = scene
12
+ end
13
+
14
+ def scene_for choice
15
+ scene_map[choice]
16
+ end
17
+
18
+ private
19
+
20
+ def scene_map
21
+ @scene_map ||= {}
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,18 @@
1
+ module Gamefic
2
+
3
+ class SceneData::YesOrNo < SceneData::Base
4
+ def yes?
5
+ input.to_s[0,1].downcase == 'y'
6
+ end
7
+ def no?
8
+ input.to_s[0,1].downcase == 'n'
9
+ end
10
+ def prompt
11
+ @prompt ||= 'Yes or No?'
12
+ end
13
+ def invalid_message
14
+ @invalid_message ||= 'Please enter Yes or No.'
15
+ end
16
+ end
17
+
18
+ end
data/lib/gamefic/shell.rb CHANGED
@@ -1,78 +1,77 @@
1
- require 'thor'
2
- require 'gamefic/engine/tty'
3
- require 'zip'
4
- require 'tmpdir'
5
- require 'yaml'
6
-
7
- module Gamefic
8
- class Shell < Thor
9
- map %w[--version -v] => :version
10
-
11
- desc "--version, -v", "Print the version"
12
- def version
13
- puts "gamefic #{Gamefic::VERSION}"
14
- end
15
-
16
- desc 'play FILE_NAME', 'Execute a compiled (.gfic) game'
17
- option :verbose, type: :boolean, aliases: :v, desc: "Don't suppress Ruby exceptions"
18
- def play(file)
19
- Dir.mktmpdir 'gamefic_' do |dir|
20
- puts 'Loading...'
21
- decompress file, dir
22
- run_game(dir)
23
- end
24
- rescue Zip::Error => e
25
- puts "'#{file}' does not appear to be a valid Gamefic file."
26
- show_exception(e) if options[:verbose]
27
- rescue StandardError => e
28
- puts "An error occurred: #{e.message}"
29
- show_exception(e) if options[:verbose]
30
- end
31
-
32
- desc 'info FILE_NAME', 'Print information about a (.gfic) game'
33
- option :verbose, type: :boolean, aliases: :v, desc: "Don't suppress Ruby exceptions"
34
- def info(file)
35
- Dir.mktmpdir 'gamefic_' do |dir|
36
- decompress file, dir
37
- metadata = YAML.load_file File.join(dir, 'metadata.yaml')
38
- metadata.each { |k, v|
39
- puts "#{k}: #{v}"
40
- }
41
- end
42
- rescue StandardError, Zip::Error => e
43
- puts "'#{file}' does not appear to be a valid Gamefic file."
44
- show_exception(e) if options[:verbose]
45
- end
46
-
47
- # Custom error message for invalid command or filename
48
- def method_missing(symbol, *args)
49
- raise UndefinedCommandError, "Could not find command or file named \"#{symbol}\"."
50
- end
51
-
52
- private
53
-
54
- def show_exception(exception)
55
- puts exception.inspect
56
- puts exception.backtrace.join("\n")
57
- end
58
-
59
- def decompress(zipfile, destination)
60
- Zip::File.open(zipfile) do |z|
61
- z.each do |entry|
62
- FileUtils.mkdir_p File.join(destination, File.dirname(entry.name))
63
- full_path = File.join(destination, entry.name)
64
- entry.extract full_path unless File.exist?(full_path)
65
- end
66
- end
67
- end
68
-
69
- def run_game(directory)
70
- story = Plot.new(Source::File.new(File.join(directory, 'scripts')))
71
- story.script 'main'
72
- story.metadata = YAML.load_file File.join(directory, 'metadata.yaml')
73
- engine = Tty::Engine.new story
74
- puts "\n"
75
- engine.run
76
- end
77
- end
78
- end
1
+ require 'thor'
2
+ require 'gamefic/engine/tty'
3
+ require 'zip'
4
+ require 'tmpdir'
5
+ require 'yaml'
6
+
7
+ module Gamefic
8
+ class Shell < Thor
9
+ map %w[--version -v] => :version
10
+
11
+ desc "--version, -v", "Print the version"
12
+ def version
13
+ puts "gamefic #{Gamefic::VERSION}"
14
+ end
15
+
16
+ desc 'play FILE_NAME', 'Execute a compiled (.gfic) game'
17
+ option :verbose, type: :boolean, aliases: :v, desc: "Don't suppress Ruby exceptions"
18
+ def play(file)
19
+ Dir.mktmpdir 'gamefic_' do |dir|
20
+ puts 'Loading...'
21
+ decompress file, dir
22
+ run_game(dir)
23
+ end
24
+ rescue Zip::Error => e
25
+ puts "'#{file}' does not appear to be a valid Gamefic file."
26
+ show_exception(e) if options[:verbose]
27
+ rescue StandardError => e
28
+ puts "An error occurred: #{e.message}"
29
+ show_exception(e) if options[:verbose]
30
+ end
31
+
32
+ desc 'info FILE_NAME', 'Print information about a (.gfic) game'
33
+ option :verbose, type: :boolean, aliases: :v, desc: "Don't suppress Ruby exceptions"
34
+ def info(file)
35
+ Dir.mktmpdir 'gamefic_' do |dir|
36
+ decompress file, dir
37
+ metadata = YAML.load_file File.join(dir, 'metadata.yaml')
38
+ metadata.each { |k, v|
39
+ puts "#{k}: #{v}"
40
+ }
41
+ end
42
+ rescue StandardError, Zip::Error => e
43
+ puts "'#{file}' does not appear to be a valid Gamefic file."
44
+ show_exception(e) if options[:verbose]
45
+ end
46
+
47
+ # Custom error message for invalid command or filename
48
+ def method_missing(symbol, *args)
49
+ raise UndefinedCommandError, "Could not find command or file named \"#{symbol}\"."
50
+ end
51
+
52
+ private
53
+
54
+ def show_exception(exception)
55
+ puts exception.inspect
56
+ puts exception.backtrace.join("\n")
57
+ end
58
+
59
+ def decompress(zipfile, destination)
60
+ Zip::File.open(zipfile) do |z|
61
+ z.each do |entry|
62
+ FileUtils.mkdir_p File.join(destination, File.dirname(entry.name))
63
+ full_path = File.join(destination, entry.name)
64
+ entry.extract full_path unless File.exist?(full_path)
65
+ end
66
+ end
67
+ end
68
+
69
+ def run_game(directory)
70
+ plot = Plot.new(Source::File.new(File.join(directory, 'scripts')))
71
+ plot.script 'main'
72
+ plot.metadata = YAML.load_file File.join(directory, 'metadata.yaml')
73
+ puts ""
74
+ Engine::Tty.start(plot)
75
+ end
76
+ end
77
+ end