Skip to content

Instantly share code, notes, and snippets.

@eregon

eregon/README Secret

Created May 17, 2010 19:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eregon/56114e98b2109df8865a to your computer and use it in GitHub Desktop.
Save eregon/56114e98b2109df8865a to your computer and use it in GitHub Desktop.
This is a test to use a gist as a git repository ...
Let's see ...
This is a sentence added locally
Let's push it :D
Working, but doesn't accept sub-directories
So,
play.rb is in bin/
all others are in lib/
Tree:
bin/
play.rb
lib/
abstract_object.rb
action.rb
main.rb
module.rb
object.rb
parser.rb
player.rb
room.rb
string.rb
synonyms.rb
world.rb
module InteractiveFiction
class AbstractObject
attr_reader :name, :description
def initialize(name, description)
@name, @description = name, description
end
def inspect
"#{self.class.simple_name} #{@name}"
end
alias :to_s :inspect
end
end
module InteractiveFiction
class Action < AbstractObject
SEPARATOR = ", "
attr_reader :code, :commands
def initialize(name, description)
super
@commands = @description["Terms"].split(SEPARATOR)
@code = Parser.parse_code @description["Code"]
end
class << self
def find(action_name, actions)
actions.find { |action|
action.commands.include? action_name
}
end
end
end
end
if RUBY_VERSION < "1.9.2"
# Backports: http://github.com/marcandre/backports
begin
# Gem load
require 'rubygems'
require 'backports/1.9'
rescue LoadError
puts $!
puts "You need to install backports(http://github.com/marcandre/backports) `gem install backports`"
puts "Notice: this cannot be considered as an external gem to help the challenge, it's just there to fill the gap between ruby versions ;)"
end
end
if __FILE__ == $0
Dir[File.join(File.dirname(__FILE__), "*.rb")].each { |f|
require f unless f == __FILE__
}
puts InteractiveFiction::Parser.new(File.expand_path("../../data/petite_cave.if", __FILE__)).parse
end
class Module
def simple_name
name.split('::').last
end
end
module InteractiveFiction
class Object < AbstractObject
SEPARATOR = ", "
NAME_TOKEN = "$"
attr_reader :terms
def initialize(name, description)
super
@terms = @description["Terms"].split(SEPARATOR)
@long_description = @description["Description"]
end
def small_description
@terms.first
end
def long_description
@long_description
end
class << self
def find(name, objects)
objects.find { |o|
o.name == name ||
o.name == "#{NAME_TOKEN}#{name}" ||
o.terms.any? { |term|
term.casecmp(name).zero?
}
}
end
end
end
end
module InteractiveFiction
class Parser
TAB = " "*2
SEPARATOR = /:(?=\s)/
NO_INDENT = /\A(?!\s)/
GROUP_TITLE = /\A(\w+)(?:\s(.\w+))?:\z/
BEGIN_CODE = /^#{Regexp.escape("{{{")}/
END_CODE = /#{Regexp.escape("}}}")}$/
ROOM_EXIT_SEPARATOR = " "
def initialize(file)
@file = file
end
def unindent(str)
str.sub(/\A#{TAB}/,'')
end
def parse
lines = IO.read(@file).lines.map(&:chomp)
lines.slice_before(NO_INDENT).reject { |o| o.all?(&:empty?) }.inject([]) { |objects,lines|
lines.shift =~ GROUP_TITLE
type, name = $1, $2
objects << InteractiveFiction.const_get(type).new(name, parse_contents(lines))
}
end
def parse_contents(lines)
lines.map { |l| unindent(l) }.slice_before(NO_INDENT).each_with_object({}) { |key_value, contents|
key, *value = key_value.map(&:strip).join("\n").split(SEPARATOR)
value = unindent value.join.lstrip
contents[key] = value
}
end
def Parser.parse_code(code)
code.lines.to_a[1...-1].join.
gsub(/\bblackboard\b/, '@blackboard') # Very weird bug, the method call fail if we add actions ???
# Else we get: "undefined method `[]' for nil:NilClass (NoMethodError)"
# Apparently instance_eval consider blackboard as a local var in this case
end
def Parser.parse_room_exits(text)
text.lines.slice_before(/\A\w+ to/).each_with_object({}) { |exit, h|
if exit.size > 1 and code = exit.join and code =~ BEGIN_CODE and code =~ END_CODE
# enter to @grate_chamber guarded by:
code =~ /(\w+) to (.+) guarded by\n/
h[$1] = [$2, parse_code($')]
else
dir, to, room = exit.first.split(ROOM_EXIT_SEPARATOR)
h[dir] = room
end
}
end
end
end
#!/usr/bin/env ruby
Dir[File.join(File.expand_path("../../lib",__FILE__), "*.rb")].each { |f| require f }
module InteractiveFiction
class Game
def initialize(story_path, options={})
@input = options.fetch(:input) { $stdin }
@output = options.fetch(:output) { $stdout }
objects = Parser.new(story_path).parse
@world = World.new(objects, @input, @output)
# The tests keep ~182000 objects! (on 390660 total)
# This can be reduced to ~140000 objects by adding "@description = nil"
# at the end of the constructor of every AbstractObject's subclass
# This is, however, slower due to GC
# GC.start
# p ObjectSpace.each_object {}
end
def play!
start!
execute_one_command! until ended?
end
def start!
@world.start!
end
def execute_one_command!
print "> " if __FILE__ == $0
@world.execute_one_command!
end
def ended?
false # The game never ends :)
end
end
end
Game = InteractiveFiction::Game
if $0 == __FILE__
story_path = ARGV[0] || File.expand_path("../../data/petite_cave.if", __FILE__)
unless story_path
warn "Usage: #{$0} STORY_FILE"
exit 1
end
game = Game.new(story_path)
game.play!
end
module InteractiveFiction
class Player
def initialize(world)
@world = world
@inventory = []
@blackboard = {}
end
def << object
if object
@inventory << object
"OK"
end
end
def >> object
@inventory.delete(object)
end
def show_inventory
if @inventory.empty?
"You're not carrying anything"
else
@inventory.map { |object|
object.small_description
}.join("\n")
end
end
# block code methods
attr_accessor :blackboard
def player_in?(room_name)
@world.current_room == Room.find(room_name, @world.rooms)
end
def player_has?(object_name)
@inventory.include? Object.find(object_name, @world.objects)
end
end
end
module InteractiveFiction
class Room < AbstractObject
attr_reader :exits, :long_description
attr_writer :world
def initialize(name, description)
super
@exits = Parser.parse_room_exits @description["Exits"]
@title = @description["Title"].rstrip.end_with!(".")
@long_description = @description["Description"]
@objects_str = @description["Objects"]
@seen = false
end
def objects
@objects ||= if objects = @objects_str
objects.split("\n").map { |name| Object.find(name, @world.objects) }
else
[]
end
end
def enter
@seen = true
end
def description
if @seen
"You're #{@title}"
else
look
end
end
def look
[self, *objects].map(&:long_description).join("\n")
end
def << object
objects << object if object
end
def >> object
objects.delete(object)
end
class << self
def find(room_name, rooms)
rooms.find { |room|
room.name == room_name
}
end
end
end
end
class String
def end_with!(suffix)
end_with?(suffix) ? self : self << suffix
end
end
module InteractiveFiction
class Synonyms < AbstractObject
SEPARATOR = ", "
def initialize(name, description)
super
@synonyms = @description.each_pair.with_object({}) { |(full, synonym), synonyms|
synonyms[full] = synonym.split(SEPARATOR)
}
@all_synonyms = @synonyms.values.reduce(:+)
end
def get_full_command(synonym)
if @all_synonyms.include? synonym
@synonyms.keys.find { |key| @synonyms[key].include?(synonym) }
else
synonym
end
end
end
end
module InteractiveFiction
class World
INPUT_SEPARATOR = " "
MOVES = %w[north east south west]
QUIT = %w[q quit]
attr_reader :rooms, :objects, :actions, :synonyms, :current_room
def initialize(objects, input, output)
@input, @output = input, output
objects.group_by(&:class).each_pair { |klass, value|
name = klass.simple_name.downcase.end_with!("s")
instance_variable_set "@#{name}", value
}
@player = Player.new(self)
@rooms.each { |room| room.world = self }
change_room @rooms.first
end
def start!
@output.puts @current_room.long_description
end
def change_room(room)
@current_room = room
room.enter
end
def enter_room(room)
room = Room.find(room, @rooms)
@output.puts room.description
change_room(room)
end
def execute_one_command!
if input = @input.gets and input.chomp! and !QUIT.include?(input)
input = input.split(INPUT_SEPARATOR)
command, args = input.shift, input
@synonyms.each { |synonym| command = synonym.get_full_command(command) }
input = args.unshift(command).join(INPUT_SEPARATOR)
if dir = @current_room.exits[input]
case dir
when Array # With Proc
room, code = dir
allow, message = @player.instance_eval(code) # [ALLOW, MESSAGE]
enter_room(room) if allow
@output.puts message
when String
enter_room dir
end
elsif MOVES.include? input
@output.puts "There is no way to go in that direction"
elsif action = Action.find(input, @actions)
message, blackboard = @player.instance_eval(action.code) # [MESSAGE, BLACKBOARD]
@output.puts message
@player.blackboard.merge!(blackboard)
else
case input
when "look"
@output.puts @current_room.look
when "inventory"
@output.puts @player.show_inventory
when /^take (.+)$/
take_object($1)
when /^drop (.+)$/
drop_object($1)
# EXTRA COMMANDS
when "dirs" # get available directions
@output.puts @current_room.exits.keys.join(", ")
else
@output.puts "Unknown command #{input}"
end
end
else
exit
end
end
def take_object(name)
@output.puts @player << (@current_room >> Object.find(name, @objects))
end
def drop_object(name)
@current_room << (@player >> Object.find(name, @objects))
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment