gamefic 1.3.2 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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