-
-
Save eregon/56114e98b2109df8865a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Module | |
def simple_name | |
name.split('::').last | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class String | |
def end_with!(suffix) | |
end_with?(suffix) ? self : self << suffix | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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