gamefic 0.6.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/bin/gamefic +3 -0
  3. data/lib/gamefic/character.rb +42 -6
  4. data/lib/gamefic/director/parser.rb +25 -25
  5. data/lib/gamefic/engine/tty.rb +14 -2
  6. data/lib/gamefic/engine.rb +8 -7
  7. data/lib/gamefic/grammar/gender.rb +1 -1
  8. data/lib/gamefic/grammar/pronouns.rb +53 -8
  9. data/lib/gamefic/grammar/verbs.rb +1 -0
  10. data/lib/gamefic/grammar/word_adapter.rb +31 -18
  11. data/lib/gamefic/html.rb +27 -13
  12. data/lib/gamefic/plot/article_mount.rb +1 -1
  13. data/lib/gamefic/plot/scene_mount.rb +27 -110
  14. data/lib/gamefic/{snapshots.rb → plot/snapshot.rb} +60 -29
  15. data/lib/gamefic/plot/you_mount.rb +1 -1
  16. data/lib/gamefic/plot.rb +56 -29
  17. data/lib/gamefic/query/base.rb +3 -1
  18. data/lib/gamefic/scene/active.rb +11 -18
  19. data/lib/gamefic/scene/base.rb +21 -0
  20. data/lib/gamefic/scene/conclusion.rb +11 -0
  21. data/lib/gamefic/scene/custom.rb +21 -0
  22. data/lib/gamefic/scene/multiple_choice/input.rb +11 -0
  23. data/lib/gamefic/scene/multiple_choice.rb +73 -0
  24. data/lib/gamefic/scene/passive.rb +17 -0
  25. data/lib/gamefic/scene/pause.rb +24 -0
  26. data/lib/gamefic/scene/question.rb +21 -0
  27. data/lib/gamefic/scene/yes_or_no.rb +30 -0
  28. data/lib/gamefic/scene.rb +10 -120
  29. data/lib/gamefic/script/base.rb +7 -2
  30. data/lib/gamefic/script.rb +4 -0
  31. data/lib/gamefic/shell/command/base.rb +38 -0
  32. data/lib/gamefic/shell/command/play.rb +51 -0
  33. data/lib/gamefic/shell/command.rb +4 -0
  34. data/lib/gamefic/shell/registry.rb +13 -0
  35. data/lib/gamefic/shell.rb +14 -71
  36. data/lib/gamefic/source/file.rb +1 -1
  37. data/lib/gamefic/source.rb +5 -0
  38. data/lib/gamefic/tester.rb +0 -1
  39. data/lib/gamefic/version.rb +1 -1
  40. data/lib/gamefic.rb +1 -6
  41. metadata +69 -61
  42. data/lib/gamefic/scene/concluded.rb +0 -22
  43. data/lib/gamefic/scene/multiplechoice.rb +0 -74
  44. data/lib/gamefic/scene/paused.rb +0 -26
  45. data/lib/gamefic/scene/yesorno.rb +0 -43
@@ -0,0 +1,73 @@
1
+ module Gamefic
2
+
3
+ # Provide a list of options and process the selection in the scene's finish
4
+ # block. After the scene is finished, the :active scene will be cued unless
5
+ # some other scene has already been prepared or cued.
6
+ #
7
+ # The finish block's input parameter receives a MultipleChoice::Input object
8
+ # instead of a String.
9
+ #
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]
17
+ end
18
+
19
+ def start actor
20
+ @current_options = @options.clone
21
+ @start.call(actor, @current_options) unless @start.nil?
22
+ tell_choices actor
23
+ end
24
+
25
+ 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]
32
+ else
33
+ index = 0
34
+ @current_options.each { |o|
35
+ if o.casecmp(input).zero?
36
+ choice = o
37
+ break
38
+ end
39
+ index += 1
40
+ }
41
+ 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
+ actor.cue :active if (actor.scene == this_scene and actor.next_scene.nil?)
49
+ end
50
+ end
51
+
52
+ def prompt
53
+ @prompt ||= "Enter a choice:"
54
+ end
55
+
56
+ def invalid_choice_message
57
+ @invalid_choice_message ||= "That's not a valid selection."
58
+ end
59
+
60
+ private
61
+
62
+ def tell_choices actor
63
+ list = '<ol class="multiple_choice">'
64
+ @current_options.each { |o|
65
+ list += "<li>#{o}</li>"
66
+ }
67
+ list += "</ol>"
68
+ actor.tell list
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,17 @@
1
+ module Gamefic
2
+
3
+ # Passive Scenes immediately cue another scene after they're finished. If no
4
+ # scene has been cued or prepared, it defaults to the :active scene.
5
+ #
6
+ class Scene::Passive < Scene::Custom
7
+ def initialize &block
8
+ @start = block
9
+ end
10
+ def start actor
11
+ this_scene = actor.scene
12
+ super
13
+ actor.cue :active if (actor.scene == this_scene and actor.next_scene.nil?)
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,24 @@
1
+ module Gamefic
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.
6
+ #
7
+ class Scene::Pause < Scene::Custom
8
+ def initialize prompt = nil, &block
9
+ @prompt = prompt
10
+ @start = block
11
+ end
12
+ 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..."
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,21 @@
1
+ module Gamefic
2
+
3
+ # Question Scenes handle a string of arbitrary input. Examples include
4
+ # asking for a password, a destination, or a topic of conversation. The
5
+ # finish block is solely responsible for processing the answer.
6
+ # After the scene is finished, the :active scene will automatically be cued
7
+ # if no other scene has been cued or prepared.
8
+ #
9
+ class Scene::Question < Scene::Custom
10
+ def initialize prompt, &block
11
+ @prompt = prompt
12
+ @finish = block
13
+ end
14
+ def finish actor, input
15
+ this_scene = actor.scene
16
+ super
17
+ actor.cue :active if (actor.scene == this_scene and actor.next_scene.nil?)
18
+ end
19
+ end
20
+
21
+ end
@@ -0,0 +1,30 @@
1
+ module Gamefic
2
+
3
+ # Prompt the user to answer "yes" or "no". The scene will accept variations
4
+ # like "YES" or "n" and normalize the answer to "yes" or "no" in the finish
5
+ # block. After the scene is finished, the :active scene will be cued if no
6
+ # other scene has been prepared or cued.
7
+ #
8
+ class Scene::YesOrNo < Scene::Custom
9
+ def initialize prompt = nil, &block
10
+ @prompt = prompt
11
+ @finish = block
12
+ end
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
23
+ this_scene = actor.scene
24
+ @finish.call actor, answer
25
+ actor.cue :active if (actor.scene == this_scene and actor.next_scene.nil?)
26
+ end
27
+ end
28
+ end
29
+
30
+ end
data/lib/gamefic/scene.rb CHANGED
@@ -1,125 +1,15 @@
1
1
  module Gamefic
2
2
 
3
- # SceneManagers handle the creation and execution of player scenes.
4
- #
5
- # @example Create a scene that lets the player select a name.
6
- # scene_managers[:get_name] = SceneManager.new do |manager|
7
- # manager.state = "Active" # Tell the Engine that this scene accepts input
8
- # manager.prompt = "Enter your name:"
9
- # manager.start do |actor, data|
10
- # actor.tell "Let's start with a formal introduction."
11
- # end
12
- # manager.finish do |actor, data|
13
- # actor[:name] = data.input
14
- # actor.tell "Howdy, #{actor[:name]}!"
15
- # data.next_cue = :active # Proceed to the default :active scene
16
- # end
17
- # end
18
- #
19
- class SceneManager
20
- attr_accessor :state
21
- attr_writer :prompt
22
-
23
- def initialize &block
24
- yield self if block_given?
25
- end
26
-
27
- # Get the SceneData class that provide data about the current event to a
28
- # Scene instance.
29
- #
30
- def data_class
31
- SceneData
32
- end
33
-
34
- # Get the name that describes this scene's state.
35
- # Two common values for the state are Active and Passive. If a scene is
36
- # Active, it is capable of accepting user input. If it is Passive, it
37
- # is probably not interactive (e.g., a cutscene) and will usually cue
38
- # an Active scene in order to continue gameplay.
39
- #
40
- # @return [String] The name of the state.
41
- def state
42
- @state ||= 'Passive'
43
- end
44
-
45
- # Get the Scene class that the SceneManager uses to prepare a scene for
46
- # the plot.
47
- #
48
- def scene_class
49
- Scene
50
- end
51
-
52
- # Define a Block to be executed when the scene starts. The Engine should
53
- # execute this block before the player is queried for input.
54
- #
55
- # @yieldparam [Character]
56
- # @yieldparam [SceneData]
57
- def start &block
58
- @start = block
59
- end
60
-
61
- # Define a Block to be executed when the scene finishes. The engine should
62
- # process user input in this block.
63
- #
64
- # @yieldparam [Character]
65
- # @yieldparam [SceneData]
66
- def finish &block
67
- @finish = block
68
- end
69
-
70
- # Prepare a new Scene for execution.
71
- #
72
- # @return [Scene]
73
- def prepare key
74
- scene_class.new(self, key)
75
- end
76
-
77
- # Get the prompt to display to the user when requesting input.
78
- #
79
- # @return [String]
80
- def prompt
81
- @prompt ||= ">"
82
- end
83
- end
84
-
85
- class SceneData
86
- attr_accessor :input, :prompt, :next_cue
3
+ module Scene
4
+ autoload :Base, 'gamefic/scene/base'
5
+ autoload :Custom, 'gamefic/scene/custom'
6
+ autoload :Active, 'gamefic/scene/active'
7
+ autoload :Passive, 'gamefic/scene/passive'
8
+ autoload :Pause, 'gamefic/scene/pause'
9
+ autoload :Conclusion, 'gamefic/scene/conclusion'
10
+ autoload :MultipleChoice, 'gamefic/scene/multiple_choice'
11
+ autoload :YesOrNo, 'gamefic/scene/yes_or_no'
12
+ autoload :Question, 'gamefic/scene/question'
87
13
  end
88
14
 
89
- class Scene
90
- attr_reader :data, :state, :key
91
-
92
- def initialize(manager, key)
93
- @manager = manager
94
- @start = manager.instance_variable_get(:@start)
95
- @finish = manager.instance_variable_get(:@finish)
96
- @state = manager.state
97
- @data = manager.data_class.new
98
- @data.prompt = manager.prompt
99
- @key = key
100
- end
101
-
102
- # Start the scene. This method is typically called by the Plot.
103
- #
104
- # @param actor [Character] The Scene's Character.
105
- def start actor
106
- return if @start.nil?
107
- @data.input = nil
108
- @start.call actor, @data
109
- end
110
-
111
- # Finish the scene. This method is typically called by the Plot.
112
- # The Finish method is responsible for processing any input received
113
- # from players.
114
- #
115
- # @param actor [Character] The Scene's Character.
116
- # @param input [String] Input received from the Character (e.g., a player command).
117
- def finish actor, input
118
- @data.next_cue ||= :active
119
- return if @finish.nil?
120
- @data.input = input
121
- @finish.call actor, @data
122
- end
123
- end
124
-
125
15
  end
@@ -11,18 +11,23 @@ module Gamefic
11
11
  def read
12
12
  raise "#read must be defined in subclasses"
13
13
  end
14
- # Get the script's path
14
+ # Get the script's path.
15
15
  #
16
16
  # @return [String]
17
17
  def path
18
18
  raise "#path must be defined in subclasses"
19
19
  end
20
- # Get the absolute path of the script's original file
20
+ # Get the absolute path of the script's original file, or its URL for
21
+ # sources that are not file-based.
21
22
  #
22
23
  # @return [String]
23
24
  def absolute_path
24
25
  raise "#absolute_path must be defined in subclasses"
25
26
  end
27
+ # Script objects are equal if their relative paths are the same. Note that
28
+ # they are still considered equal if their absolute paths are different,
29
+ # or even if they come from different types of sources.
30
+ #
26
31
  # @param other[Script::Base]
27
32
  # @return [Boolean]
28
33
  def==(other)
@@ -1,5 +1,9 @@
1
1
  module Gamefic
2
2
 
3
+ # Script classes provide code to be executed in Plots. They are accessed
4
+ # through Source classes, e.g., a Source::Text object is used to find
5
+ # Source::Files.
6
+ #
3
7
  module Script
4
8
  autoload :Base, 'gamefic/script/base'
5
9
  autoload :File, 'gamefic/script/file'
@@ -0,0 +1,38 @@
1
+ require 'gamefic/shell'
2
+ require 'slop'
3
+
4
+ class Gamefic::Shell::Command::Base
5
+ # Execute the command
6
+ #
7
+ def run input
8
+ raise "Unimplemented command"
9
+ end
10
+
11
+ # Get the options for the command
12
+ #
13
+ # @return [Slop::Options]
14
+ def options
15
+ @options ||= Slop::Options.new
16
+ end
17
+
18
+ # Get the help documentation for this command.
19
+ #
20
+ # @return [String]
21
+ def help
22
+ optons.to_s
23
+ end
24
+
25
+ protected
26
+
27
+ # @return [Slope::Result]
28
+ def parse input
29
+ parser.parse input
30
+ end
31
+
32
+ private
33
+
34
+ # @return [Slop::Parser]
35
+ def parser
36
+ @parser ||= Slop::Parser.new(options)
37
+ end
38
+ end
@@ -0,0 +1,51 @@
1
+ require 'zip'
2
+ require 'tmpdir'
3
+ require 'gamefic/engine/tty'
4
+ require 'gamefic/shell'
5
+ require 'yaml'
6
+
7
+ class Gamefic::Shell::Command::Play < Gamefic::Shell::Command::Base
8
+ include Gamefic
9
+
10
+ def run input
11
+ result = parse input
12
+ file = result.arguments[1]
13
+ raise "File not specified." if file.nil?
14
+ raise "'#{file}' does not exist." if !File.exist?(file)
15
+ raise "'#{file}' is a directory." if File.directory?(file)
16
+ play file
17
+ end
18
+
19
+ private
20
+
21
+ def decompress(zipfile, destination)
22
+ Zip::File.open(zipfile) do |z|
23
+ z.each do |entry|
24
+ FileUtils.mkdir_p File.join(destination, File.dirname(entry.name))
25
+ full_path = File.join(destination, entry.name)
26
+ if !File.exist?(full_path)
27
+ entry.extract full_path
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ def play file
34
+ Dir.mktmpdir 'gamefic_' do |dir|
35
+ puts "Loading..."
36
+ story = Plot.new(Source::File.new(File.join(dir, 'scripts')))
37
+ begin
38
+ decompress file, dir
39
+ rescue Exception => e
40
+ puts "'#{file}' does not appear to be a valid Gamefic file."
41
+ #puts "Error: #{e.message}"
42
+ #exit 1
43
+ end
44
+ story.script 'main'
45
+ story.metadata = YAML.load_file File.join(dir, 'metadata.yaml')
46
+ engine = Tty::Engine.new story
47
+ puts "\n"
48
+ engine.run
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,4 @@
1
+ module Gamefic::Shell::Command
2
+ autoload :Base, 'gamefic/shell/command/base'
3
+ autoload :Play, 'gamefic/shell/command/play'
4
+ end
@@ -0,0 +1,13 @@
1
+ module Gamefic
2
+ class Shell
3
+ module Registry
4
+ @commands = {}
5
+ def self.register cmd, cls
6
+ @commands[cmd] = cls
7
+ end
8
+ def self.get_command_class cmd
9
+ @commands[cmd]
10
+ end
11
+ end
12
+ end
13
+ end
data/lib/gamefic/shell.rb CHANGED
@@ -1,82 +1,25 @@
1
- require 'zip'
2
- require 'tmpdir'
3
- require 'getoptlong'
4
- require 'gamefic/engine/tty'
5
-
6
1
  module Gamefic
7
2
 
8
3
  class Shell
9
- attr_accessor :argv
4
+ autoload :Command, 'gamefic/shell/command'
5
+
10
6
  def initialize
7
+ @commands = {}
8
+ end
11
9
 
10
+ def register cmd, cls
11
+ @commands[cmd] = cls
12
12
  end
13
+
13
14
  def execute
14
- if ARGV.length == 0
15
- ARGV.push 'help'
16
- end
17
- cmd = ARGV.shift
18
- case cmd
19
- when 'play'
20
- play ARGV.shift
21
- when 'help'
22
- help ARGV.shift
23
- else
24
- play cmd
15
+ command = ARGV[0]
16
+ cls = @commands[command]
17
+ if cls.nil?
18
+ Gamefic::Shell::Command::Play.new.run(['play'] + ARGV)
19
+ else
20
+ cls.new.run ARGV
25
21
  end
26
22
  end
27
- private
28
- def play file
29
- if !File.exist?(file)
30
- puts "'#{file}' does not exist."
31
- exit 1
32
- end
33
- if File.directory?(file)
34
- puts "'#{file}' is not a Gamefic file."
35
- exit 1
36
- end
37
- Dir.mktmpdir 'gamefic_' do |dir|
38
- puts "Loading..."
39
- story = Plot.new(Source.new(dir + '/scripts'))
40
- begin
41
- decompress file, dir
42
- rescue Exception => e
43
- puts "'#{file}' does not appear to be a valid Gamefic file."
44
- puts e.backtrace
45
- exit 1
46
- end
47
- story.load dir + '/main'
48
- engine = Tty::Engine.new story
49
- puts "\n"
50
- engine.run
51
- end
52
- end
53
- def help command
54
- shell_script = File.basename($0)
55
- case command
56
- when "play"
57
- puts <<EOS
58
- #{shell_script} play [file]
59
- Play a Gamefic file on the command line.
60
- EOS
61
- when nil, "help"
62
- puts <<EOS
63
- #{shell_script} play [file] - play a Gamefic file
64
- #{shell_script} help - display this message
65
- #{shell_script} help [command] - display info about command
66
- EOS
67
- else
68
- puts "Unrecognized command '#{command}'"
69
- exit 1
70
- end
71
- end
72
- def decompress(zipfile, destination)
73
- Zip::File.open(zipfile) do |z|
74
- z.each do |entry|
75
- FileUtils.mkdir_p "#{destination}/#{File.dirname(entry.name)}"
76
- entry.extract "#{destination}/#{entry.name}"
77
- end
78
- end
79
- end
80
23
  end
81
-
24
+
82
25
  end
@@ -10,7 +10,7 @@ module Gamefic
10
10
  def export path
11
11
  @directories.each { |directory|
12
12
  @@extensions.each { |ext|
13
- abs_file = directory + '/' + path + ext
13
+ abs_file = File.join(directory, path + ext)
14
14
  if File.file?(abs_file)
15
15
  return Script::File.new(abs_file, path)
16
16
  end
@@ -1,5 +1,10 @@
1
1
  module Gamefic
2
2
 
3
+ # Plots use Source classes to fetch scripts to be executed. The most common
4
+ # type of Source class is Source::File, which searches for scripts in
5
+ # a predefined list of directories on the filesystem, similar to the way
6
+ # that Kernel#require works.
7
+ #
3
8
  module Source
4
9
  autoload :Base, 'gamefic/source/base'
5
10
  autoload :File, 'gamefic/source/file'
@@ -13,7 +13,6 @@ module Gamefic
13
13
  actor[:test_queue] = queue
14
14
  actor[:test_queue_length] = queue.length
15
15
  actor[:test_queue_scene] = actor.scene
16
- actor[:testing] = true
17
16
  end
18
17
  end
19
18
 
@@ -1,3 +1,3 @@
1
1
  module Gamefic
2
- VERSION = '0.6.1'
2
+ VERSION = '1.0.0'
3
3
  end
data/lib/gamefic.rb CHANGED
@@ -7,11 +7,6 @@ require 'gamefic/serialized'
7
7
  require 'gamefic/entity'
8
8
  require 'gamefic/character'
9
9
  require "gamefic/scene"
10
- require "gamefic/scene/active"
11
- require "gamefic/scene/concluded"
12
- require "gamefic/scene/paused"
13
- require "gamefic/scene/multiplechoice"
14
- require "gamefic/scene/yesorno"
15
10
  require "gamefic/query"
16
11
  require "gamefic/action"
17
12
  require "gamefic/syntax"
@@ -20,4 +15,4 @@ require "gamefic/director"
20
15
  require "gamefic/plot"
21
16
  require "gamefic/engine"
22
17
  require "gamefic/direction"
23
- require "gamefic/snapshots"
18
+ require "gamefic/version"