Created
December 8, 2012 01:43
-
-
Save feltnerm/4238103 to your computer and use it in GitHub Desktop.
Psuedo-Interactive Fiction Interpreter. Done via recursive descent.
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/local/bin/ruby | |
# | |
# dungeon.rb: dungeon state + operations to add/remove objs, open doors, etc. | |
# - Each object is represented as a simple string, and the intent is that | |
# each object would be in just one location. | |
# | |
# This file contains unit test code; to run it, type | |
# ruby dungeon.rb | |
# @author: Rob Hasker | |
# uncomment following require to enable debugging | |
#require 'ruby-debug' | |
# Captures state of the dungeon | |
class Dungeon | |
attr_reader :rooms # type: Hash(String -> Room) | |
attr_reader :pack # type: BackPack | |
attr_accessor :current_room # type: Room - room character is in | |
# initialize: init dungeon w/ no rooms and an empty backpack | |
def initialize | |
@rooms = {} | |
@pack = BackPack.new | |
end | |
# returns the item's location, either a room or the backpack | |
# If the item is not in the dungeon, returns nil. | |
def location_of(item) | |
return @pack if @pack.has?(item) | |
@rooms.each { |id, r| | |
return r if r.has?(item) | |
} | |
return nil | |
end | |
# Add the room to the dungeon | |
def add(room) | |
raise "Illegal room: #{room.inspect}" unless room.class == Room | |
puts "Room #{room.id} already exists." if @rooms[room.id] | |
@rooms[room.id] = room | |
@current_room = room unless @current_room | |
end | |
# removes item from dungeon, effectively destroying it | |
def remove(item) | |
loc = location_of(item) | |
loc.remove(item) | |
end | |
end | |
###################################################################### | |
# places where objects can be | |
class Location | |
attr_accessor :contents # type: Hash(String -> Boolean) | |
# (effectively a set) | |
# initialize so the list of objects is empty | |
def initialize | |
@contents = {} | |
end | |
# does this location have the specified object? | |
def has?(item) | |
! @contents[item].nil? | |
end | |
# add object to this location if it is not already present | |
def add(item) | |
if ! has?(item) | |
@contents[item] = true | |
end | |
end | |
# remove object from this location | |
def remove(item) | |
@contents.delete(item) | |
end | |
protected | |
# return list of contents for status reports | |
def sorted_contents | |
@contents.keys.sort | |
end | |
end | |
# information about a particular room | |
class Room < Location | |
attr_reader :id # string identifier for room | |
attr_reader :exits # hash from directions to room names | |
# giving open doors out of the room | |
attr_accessor :entry, :action # source code to execute on entry | |
# and after executing certain actions | |
# room identifier + code to execute | |
def initialize(id, code = []) | |
super() | |
@id = id | |
@exits = {} | |
action_index = code.index('action:') | |
if action_index | |
@entry = code.shift(action_index) | |
code.shift # skip the action: label | |
@action = code | |
else | |
@entry = code | |
@action = [] | |
end | |
end | |
# list all objects in the room | |
def describe_objects | |
sorted_contents.each { |obj| | |
article = 'aeiou'.include?(obj[0..0].downcase) ? 'an' : 'a' | |
puts "There is #{article} #{obj} on the ground." | |
} | |
end | |
# display status of room as specified in writeup | |
def show_status | |
puts "Status for room #{id}:" | |
if @contents.empty? | |
puts " The room is empty." | |
else | |
puts " Contents: #{sorted_contents.join(', ')}" | |
end | |
if @exits.empty? | |
puts " No exits." | |
else | |
doors = [] | |
doors << 'north' if @exits[:north] | |
doors << 'south' if @exits[:south] | |
doors << 'east' if @exits[:east] | |
doors << 'west' if @exits[:west] | |
puts " There are exits to the #{doors.join(', ')}." | |
end | |
end | |
# returns true iff there is a door in the specified direction | |
# Direction must be :north, :south, :east, or ;west | |
def open?(direction) | |
! @exits[direction].nil? | |
end | |
# open a door leading to the given destination | |
def open(direction, destination) | |
@exits[direction] = destination | |
end | |
end | |
# the status of the character's backpack | |
class BackPack < Location | |
# show status of backpack by listing its contents | |
# The list is in alphabetical order for concreteness. | |
def show_status | |
if @contents.empty? | |
puts "Your backpack is empty." | |
else | |
puts "Backpack contents:" | |
puts " #{sorted_contents.join(', ')}" | |
end | |
end | |
end | |
# class for exception to raise when the character dies | |
class Death < Exception | |
end | |
if __FILE__ == $0 | |
# code to test the dungeon classes; to execute, type | |
# ruby dungeon.rb | |
class AssertionError < StandardError | |
end | |
def assert a | |
raise AssertionError, "#{a.inspect}" unless a | |
end | |
# uncomment following to check assertion works: | |
#assert false | |
# test dungeon and related classes: | |
d = Dungeon.new | |
a = Room.new('A') | |
b = Room.new('B', [:one, :two, 'action:', :three, :four]) | |
assert b.entry == [:one, :two] | |
assert b.action == [:three, :four] | |
d.add(a) | |
d.add(b) | |
assert d.current_room == a | |
assert d.location_of('pear').nil? | |
a.add('pear') | |
assert d.location_of('rock').nil? | |
d.current_room.add('rock') | |
b.add('hammer') | |
assert a.has? 'pear' | |
assert a.has? 'rock' | |
assert b.has? 'hammer' | |
assert !a.has?('hammer') | |
d.pack.add('wine') | |
assert d.location_of('hammer') == b | |
assert d.location_of('rock') == a | |
assert d.location_of('wine') == d.pack | |
assert !a.open?(:east) | |
a.open(:east, b) | |
assert a.open?(:east) | |
d.remove('wine') | |
assert d.location_of('wine').nil? | |
assert !d.pack.has?('wine') | |
d.remove('rock') | |
assert !a.has?('rock') | |
assert d.location_of('rock').nil? | |
puts "All tests pass." | |
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
<story> -> <room_list> do: <command_list> | |
<room_list> -> <room_declaration> { <room_declaration> } | |
<room_declaration> -> <room_name> '{' <instruction_list> '}' | |
<instruction_list> -> { <instruction> } | |
<instruction> -> add <object> | |
-> open <direction> to <room_name> | |
-> print '(' <message_text> ') | |
-> if <test> then <instruction_list> [ else <instruction_list> ] end | |
<direction> -> north | south | east | west | |
<test> -> <object> in pack | |
<command_list> -> { <command> } | |
<command> -> take <object> | |
-> go <direction> | |
-> status | |
-> inventory |
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/local/bin/ruby | |
############################################################################# | |
# | |
# ifi.rb: interactive fiction intepreter | |
# | |
# @author: Mark Feltner | |
############################################################################# | |
require 'dungeon' | |
# print an error message and quit - useful for reporting parse erros | |
# rest_of_code is an array of strings and helps in knowing what | |
# part of the input has not been parsed yet. Note that you do not | |
# have to call this function - reporting errors can be helpful | |
# but is not required for the assignment. | |
def report_error(message, rest_of_code = []) | |
abort "Error: #{message}\n -- remaining code: #{rest_of_code.join(' ')}" | |
end | |
# class to hold the results of parsing the input file | |
class Story | |
def initialize | |
@dungeon = Dungeon.new | |
end | |
# Divides input code into two segments. The first is room definitions | |
# and instructions, the second is the command list. | |
def split_code(code) | |
index = code.index("do:") | |
return code.slice(0..index-1), code.slice(index+1..code.length) | |
end | |
# Parses the room definitions list and executes the commands of the story | |
# <story> -> <room_list> do: <command_list> | |
def do_story(code) | |
room_list, command_list = split_code(code) | |
load_room_list(room_list) | |
do_command_list(command_list) | |
end | |
protected | |
# run the instructions for a room. | |
# This needs to create a copy of the code so that any changes | |
# to the list (such as removing items that have been parsed) | |
# will not result in permanent changes to the code for the room. | |
def run(code) | |
# create copy of the room | |
copy = code.clone | |
# execute the code | |
do_instruction_list(copy) | |
end | |
###################### | |
# ROOM LIST | |
###################### | |
# Iterate over the list of room defintions until the end | |
# <room_list> -> <room_declaration> { <room_declaration> } | |
def load_room_list(code) | |
while !code.empty? and code.first != "do:" | |
parse_room_declaration(code) | |
end | |
end | |
# Parse a room declaration | |
# <room_declaration> -> <room_name> '{' <instruction_list> '}' | |
def parse_room_declaration(code) | |
room_name = code.shift | |
if code.first == '{' | |
room = Room.new(room_name, code[1...code.index("}")]) | |
@dungeon.add(room) | |
end | |
end | |
# Iterate over the list of instructions | |
# Execute is recursively passed down to each function and controls | |
# whether or not that command is executed rather than just syntactically | |
# parsed. | |
# <instruction_list> -> { <instruction> } | |
def do_instruction_list(code, execute = true) | |
while !code.empty? and code.first != '}' | |
do_instruction(code, execute) | |
end | |
end | |
# Handles a single instruction | |
# <instruction> -> add <object> | |
# -> open <direction> to <room_name> | |
# -> print '(' <message_text> ') | |
# -> if <test> then <instruction_list> | |
# [ else <instruction_list> ] end | |
def do_instruction(code, execute) | |
instruction = code.shift | |
if instruction == "add" | |
do_instruction_add(code, execute) | |
elsif instruction == "open" | |
do_instruction_open(code, execute) | |
elsif instruction == "print" | |
do_instruction_print(code, execute) | |
elsif instruction == "if" | |
do_instruction_if(code, execute) | |
end | |
end | |
# Add an object to the room | |
# add <object> | |
def do_instruction_add(code, execute) | |
object = code.shift | |
if execute | |
if @dungeon.location_of(object) == nil | |
@dungeon.current_room.add(object) | |
puts "The #{object} is on the ground." | |
end | |
end | |
end | |
# Open a door to another room | |
# open <direction> to <room_name> | |
def do_instruction_open(code, execute) | |
direction = code.shift.to_sym | |
room_name = code.shift(2)[1] | |
if execute | |
if !@dungeon.current_room.open?(direction) | |
@dungeon.current_room.open(direction, room_name) | |
puts "A door to the #{direction} opens." | |
end | |
end | |
end | |
# Print something | |
# print '(' <message_text> ')' | |
def do_instruction_print(code, execute) | |
message_text = "" | |
code.shift | |
while code.first != ')' | |
message_text << code.shift | |
message_text << " " | |
end | |
if execute | |
puts message_text.chomp " " | |
end | |
code.shift | |
end | |
# Evaluates a test condition for the 'if' command and determines whether | |
# the following <instruction_list> should be executed rather than just | |
# syntactically parsed. | |
# if <test> then <instruction_list> [ else <instruction_list> ] end | |
def do_instruction_if(code, execute) | |
if execute | |
condition = test_condition(code) | |
if condition | |
do_instruction_then(code, true, false) | |
else | |
do_instruction_then(code, false, true) | |
end | |
else | |
condition = test_condition(code) | |
if condition | |
do_instruction_then(code, false, false) | |
else | |
do_instruction_then(code, false, false) | |
end | |
end | |
end | |
# Parses and potentially executes the <instruction_list> when a 'then' | |
# has been found. | |
def do_instruction_then(code, execute_then, execute_else) | |
code.shift # shift over 'then' | |
while !code.empty? | |
if code.first == "else" | |
do_instruction_else(code, execute_else) | |
elsif code.first == "end" | |
code.shift | |
return | |
else | |
do_instruction(code, execute_then) | |
end | |
end | |
end | |
# Parses and potentially executes the <instruction_list> when an 'else' | |
# has been found. | |
def do_instruction_else(code, execute) | |
while !code.empty? and code.first != "end" | |
do_instruction(code, execute) | |
end | |
end | |
# Test the condition following an 'if' | |
def test_condition(code) | |
object = code.first | |
code.shift(3) | |
return @dungeon.pack.has?(object) | |
end | |
###################### | |
# COMMAND LIST | |
###################### | |
# Descends into each command found in the <command_list> | |
# <command_list> -> { <command> } | |
def do_command_list(code) | |
run(@dungeon.current_room.entry) | |
while !code.empty? | |
do_command(code) | |
end | |
end | |
# Handles a single command | |
def do_command(code) | |
command = code.shift | |
if command == "status" | |
do_command_status(code) | |
elsif command == "take" | |
do_command_take(code) | |
elsif command == "go" | |
do_command_go(code) | |
elsif command == "inventory" | |
do_command_inventory(code) | |
end | |
end | |
# Executes the 'status' command to show the current room's stuff and doors | |
def do_command_status(code) | |
puts "> status" | |
@dungeon.current_room.show_status | |
end | |
# Executes the 'inventory' command to show the player's current pack | |
def do_command_inventory(code) | |
puts "> inventory" | |
@dungeon.pack.show_status | |
end | |
# Executes the 'take' command which puts an object from the room to the | |
# player's pack | |
# take <object> | |
def do_command_take(code) | |
item = code.shift | |
puts "> take #{item}" | |
if @dungeon.current_room.has?(item) | |
@dungeon.pack.add(item) | |
@dungeon.current_room.remove(item) | |
puts "You pick up the #{item}." | |
else | |
puts "Cannot find #{item}." | |
end | |
end | |
# Executes the 'go' command which takes the player to anothe room | |
# go <direction> | |
def do_command_go(code) | |
direction = code.shift | |
puts "> go #{direction}" | |
if @dungeon.current_room.open?(direction.to_sym) | |
destination = @dungeon.current_room.exits[direction.to_sym] | |
@dungeon.current_room = @dungeon.rooms[destination] | |
run(@dungeon.current_room.entry) | |
else | |
puts "You bump your nose on the wall." | |
end | |
end | |
end | |
# returns an array of words read from stdin | |
# type: none -> void | |
def get_input() | |
input = [] | |
while line = gets | |
input << line | |
end | |
input.each { |line| line.chomp! } | |
#puts ">>>>>>>>"; input.each { |l| puts l }; puts ">>>>>>>>" | |
# remove comments | |
input.delete_if { |line| line =~ /^\s*#/ } | |
result = input.join(' ').split | |
#puts ">>>>>>>>"; puts result.join(' '); puts "<<<<<<<<<" | |
return result | |
end | |
# input: array of lines; if empty, reads lines from stdin | |
# Read input, break it into two components, set up the world, | |
# parse the story, and run the story. | |
def main(input = "") | |
if input.empty? | |
input = get_input | |
else | |
input = input.join(' ').split | |
end | |
story = Story.new | |
story.do_story(input) | |
end | |
if __FILE__ == $0 | |
if false # debugging | |
main(['Minimal { } do: status']) | |
main(['Single { add crate } do: take crate']) | |
main(['Single { print ( Hello, world! ) } do: status']) | |
main(['Single { add crate open north to Single', | |
' if crate in pack then print ( found ) end }', | |
' do: take crate go north']) | |
#main(['Multiple { add crate open north to Single', | |
# ' if womp in pack then print ( womp ) end }', | |
# ' do: take womp go north status inventory']) | |
# expected output: | |
# > status | |
# Status for room Minimal: | |
# The room is empty. | |
# No exits. | |
# The crate is on the ground. | |
# > take crate | |
# You pick up the crate. | |
# hello, world! | |
# > status | |
# Status for room Single: | |
# The room is empty. | |
# No exits. | |
# The crate is on the ground. | |
# A door to the north opens. | |
# > take crate | |
# You pick up the crate. | |
# > go north | |
# found | |
else | |
main | |
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/local/bin/ruby | |
############################################################################# | |
# | |
# ifi.rb: interactive fiction intepreter | |
# | |
# @author: Mark Feltner | |
# | |
# ALL EXTENSIONS WORKING EXCEPT CONJUNCT AND DISJUNCT (although they are | |
# very, very close) | |
############################################################################# | |
require 'dungeon' | |
# print an error message and quit - useful for reporting parse erros | |
# rest_of_code is an array of strings and helps in knowing what | |
# part of the input has not been parsed yet. Note that you do not | |
# have to call this function - reporting errors can be helpful | |
# but is not required for the assignment. | |
def report_error(message, rest_of_code = []) | |
abort "Error: #{message}\n -- remaining code: #{rest_of_code.join(' ')}" | |
end | |
# class to hold the results of parsing the input file | |
class Story | |
def initialize | |
@dungeon = Dungeon.new | |
end | |
# Divides input code into two segments. The first is room definitions | |
# and instructions, the second is the command list. | |
def split_code(code) | |
index = code.index("do:") | |
return code.slice(0..index-1), code.slice(index+1..code.length) | |
end | |
# Parses the room definitions list and executes the commands of the story | |
# <story> -> <room_list> do: <command_list> | |
def do_story(code) | |
room_list, command_list = split_code(code) | |
load_room_list(room_list) | |
do_command_list(command_list) | |
rescue Death | |
end | |
protected | |
# run the instructions for a room. | |
# This needs to create a copy of the code so that any changes | |
# to the list (such as removing items that have been parsed) | |
# will not result in permanent changes to the code for the room. | |
def run(code) | |
# create copy of the room | |
copy = code.clone | |
# execute the code | |
do_instruction_list(copy) | |
end | |
###################### | |
# ROOM LIST | |
###################### | |
# Iterate over the list of room defintions until the end | |
# <room_list> -> <room_declaration> { <room_declaration> } | |
def load_room_list(code) | |
while !code.empty? and code.first != "do:" | |
parse_room_declaration(code) | |
end | |
end | |
# Parse a room declaration | |
# <room_declaration> -> <room_name> '{' <instruction_list> '}' | |
def parse_room_declaration(code) | |
room_name = code.shift | |
if code.first == '{' | |
room = Room.new(room_name, code[1...code.index("}")]) | |
@dungeon.add(room) | |
end | |
end | |
# Iterate over the list of instructions | |
# Execute is recursively passed down to each function and controls | |
# whether or not that command is executed rather than just syntactically | |
# parsed. | |
# <instruction_list> -> { <instruction> } | |
def do_instruction_list(code, execute = true) | |
while !code.empty? and code.first != '}' | |
do_instruction(code, execute) | |
end | |
end | |
# Handles a single instruction | |
# <instruction> -> add <object> | |
# -> open <direction> to <room_name> | |
# -> print '(' <message_text> ') | |
# -> if <test> then <instruction_list> | |
# [ else <instruction_list> ] end | |
# -> | |
def do_instruction(code, execute) | |
instruction = code.shift | |
if instruction == "add" | |
do_instruction_add(code, execute) | |
elsif instruction == "open" | |
do_instruction_open(code, execute) | |
elsif instruction == "print" | |
do_instruction_print(code, execute) | |
elsif instruction == "if" | |
do_instruction_if(code, execute) | |
elsif instruction == "look" | |
do_instruction_look(code, execute) | |
elsif instruction == "move" | |
do_instruction_move(code, execute) | |
elsif instruction == "destroy" | |
do_instruction_destroy(code, execute) | |
elsif instruction == "die" | |
do_instruction_die(code, execute) | |
end | |
end | |
def do_instruction_look(code, execute) | |
if execute | |
@dungeon.current_room.describe_objects | |
end | |
end | |
def do_instruction_move(code, execute) | |
object = code.shift | |
location = code.shift(2)[1] | |
object_location = @dungeon.location_of(object) | |
if execute | |
object_location.remove(object) | |
@dungeon.rooms[location].add(object) | |
end | |
end | |
def do_instruction_destroy(code, execute) | |
item = code.shift | |
if execute | |
@dungeon.remove(item) | |
end | |
end | |
def do_instruction_die(code, execute) | |
if execute | |
puts "You die." | |
raise Death.new | |
end | |
end | |
# Add an object to the room | |
# add <object> | |
def do_instruction_add(code, execute) | |
object = code.shift | |
if execute | |
if @dungeon.location_of(object) == nil | |
@dungeon.current_room.add(object) | |
puts "The #{object} is on the ground." | |
end | |
end | |
end | |
# Open a door to another room | |
# open <direction> to <room_name> | |
def do_instruction_open(code, execute) | |
direction = code.shift.to_sym | |
room_name = code.shift(2)[1] | |
if execute | |
if !@dungeon.current_room.open?(direction) | |
@dungeon.current_room.open(direction, room_name) | |
puts "A door to the #{direction} opens." | |
end | |
end | |
end | |
# Print something | |
# print '(' <message_text> ')' | |
def do_instruction_print(code, execute) | |
message_text = "" | |
code.shift | |
while code.first != ')' | |
message_text << code.shift | |
message_text << " " | |
end | |
if execute | |
puts message_text.chomp " " | |
end | |
code.shift | |
end | |
# Evaluates a test condition for the 'if' command and determines whether | |
# the following <instruction_list> should be executed rather than just | |
# syntactically parsed. | |
# if <test> then <instruction_list> [ else <instruction_list> ] end | |
def do_instruction_if(code, execute) | |
if execute | |
condition = do_instruction_test(code) | |
#condition = test_condition(code) | |
if condition | |
do_instruction_then(code, true, false) | |
else | |
do_instruction_then(code, false, true) | |
end | |
else | |
condition = do_instruction_test(code) | |
#condition = test_condition(code) | |
if condition | |
do_instruction_then(code, false, false) | |
else | |
do_instruction_then(code, false, false) | |
end | |
end | |
end | |
# Parses and potentially executes the <instruction_list> when a 'then' | |
# has been found. | |
def do_instruction_then(code, execute_then, execute_else) | |
code.shift # shift over 'then' | |
while !code.empty? | |
if code.first == "else" | |
do_instruction_else(code, execute_else) | |
elsif code.first == "end" | |
code.shift | |
return | |
else | |
do_instruction(code, execute_then) | |
end | |
end | |
end | |
# Parses and potentially executes the <instruction_list> when an 'else' | |
# has been found. | |
def do_instruction_else(code, execute) | |
while !code.empty? and code.first != "end" | |
do_instruction(code, execute) | |
end | |
end | |
# Evaluate a conditional test | |
def do_instruction_test(code) | |
condition = do_disjunct(code) | |
while code.first == "or" | |
code.shift | |
if condition | |
do_disjunct(code) | |
else | |
condition = do_disjunct(code) | |
end | |
end | |
return condition | |
end | |
# Evaluate a disjunct condition | |
def do_disjunct(code) | |
condition = do_conjunct(code) | |
while code.first == "and" | |
code.shift | |
if condition | |
condition = do_conjunct(code) | |
else | |
do_conjunct(code) | |
end | |
end | |
return condition | |
end | |
# Evaluate a conjunct condition | |
def do_conjunct(code) | |
if code.first == "(" | |
while !code.empty? and !code.first == ')' | |
code.shift | |
do_instruction_test(code) | |
code.shift | |
end | |
else | |
if code.first == "not" | |
code.shift | |
result = !do_test(code) | |
#puts result | |
return result | |
else | |
result = do_test(code) | |
#puts result | |
return result | |
end | |
end | |
end | |
# Parses and runs one of the conditonal tests | |
def do_test(code) | |
object = code.first | |
code.shift | |
if code.first == "in" | |
code.shift | |
if code.first == "pack" | |
code.shift | |
return test_in_pack(object) | |
elsif code.first == "room" | |
code.shift | |
return test_in_room(object) | |
end | |
elsif code.first == "exists" | |
code.shift | |
return test_exists(object) | |
end | |
end | |
# True if the object exists in the dungeon or the player's pack | |
def test_exists(object) | |
#puts ">> EXIST? #{object}" | |
return !@dungeon.location_of(object).nil? | |
end | |
# True if the object exists in the current player's pack | |
def test_in_pack(object) | |
#puts ">> PACK? #{object}" | |
return @dungeon.pack.has?(object) | |
end | |
# True if the object is in the current room | |
def test_in_room(object) | |
#puts ">> ROOM? #{object}" | |
return @dungeon.current_room.has?(object) | |
end | |
###################### | |
# COMMAND LIST | |
###################### | |
# Descends into each command found in the <command_list> | |
# <command_list> -> { <command> } | |
def do_command_list(code) | |
run(@dungeon.current_room.entry) | |
while !code.empty? | |
do_command(code) | |
end | |
end | |
# Handles a single command | |
def do_command(code) | |
command = code.shift | |
if command == "status" | |
do_command_status(code) | |
elsif command == "take" | |
do_command_take(code) | |
run(@dungeon.current_room.action) | |
elsif command == "go" | |
do_command_go(code) | |
elsif command == "inventory" | |
do_command_inventory(code) | |
elsif command == "look" | |
do_command_look(code) | |
elsif command == "throw" | |
do_command_throw(code) | |
run(@dungeon.current_room.action) | |
elsif command == "drop" | |
do_command_drop(code) | |
run(@dungeon.current_room.action) | |
end | |
end | |
def do_command_drop(code) | |
object = code.shift | |
puts "> drop #{object}" | |
if @dungeon.pack.has?(object) | |
@dungeon.pack.remove(object) | |
@dungeon.current_room.add(object) | |
puts "You drop the #{object}." | |
else | |
puts "You do not have the #{object}." | |
end | |
end | |
def do_command_look(code) | |
puts "> look" | |
@dungeon.current_room.describe_objects | |
end | |
def do_command_throw(code) | |
obj_to_throw = code.shift | |
obj_to_hit = code.shift(2)[1] | |
puts "> throw #{obj_to_throw} at #{obj_to_hit}" | |
if @dungeon.pack.has?(obj_to_throw) | |
@dungeon.pack.remove(obj_to_throw) | |
if @dungeon.current_room.has?(obj_to_hit) | |
@dungeon.current_room.add(obj_to_throw) | |
puts "You hit the #{obj_to_hit}." | |
else | |
puts "Cannot hit #{obj_to_hit}." | |
end | |
else | |
"Cannot throw #{obj_to_throw}." | |
end | |
end | |
# Executes the 'status' command to show the current room's stuff and doors | |
def do_command_status(code) | |
puts "> status" | |
@dungeon.current_room.show_status | |
end | |
# Executes the 'inventory' command to show the player's current pack | |
def do_command_inventory(code) | |
puts "> inventory" | |
@dungeon.pack.show_status | |
end | |
# Executes the 'take' command which puts an object from the room to the | |
# player's pack | |
# take <object> | |
def do_command_take(code) | |
item = code.shift | |
puts "> take #{item}" | |
if @dungeon.current_room.has?(item) | |
@dungeon.pack.add(item) | |
@dungeon.current_room.remove(item) | |
puts "You pick up the #{item}." | |
else | |
puts "Cannot find #{item}." | |
end | |
end | |
# Executes the 'go' command which takes the player to anothe room | |
# go <direction> | |
def do_command_go(code) | |
direction = code.shift | |
puts "> go #{direction}" | |
if @dungeon.current_room.open?(direction.to_sym) | |
destination = @dungeon.current_room.exits[direction.to_sym] | |
@dungeon.current_room = @dungeon.rooms[destination] | |
run(@dungeon.current_room.entry) | |
else | |
puts "You bump your nose on the wall." | |
end | |
end | |
end | |
# returns an array of words read from stdin | |
# type: none -> void | |
def get_input() | |
input = [] | |
while line = gets | |
input << line | |
end | |
input.each { |line| line.chomp! } | |
#puts ">>>>>>>>"; input.each { |l| puts l }; puts ">>>>>>>>" | |
# remove comments | |
input.delete_if { |line| line =~ /^\s*#/ } | |
result = input.join(' ').split | |
#puts ">>>>>>>>"; puts result.join(' '); puts "<<<<<<<<<" | |
return result | |
end | |
# input: array of lines; if empty, reads lines from stdin | |
# Read input, break it into two components, set up the world, | |
# parse the story, and run the story. | |
def main(input = "") | |
if input.empty? | |
input = get_input | |
else | |
input = input.join(' ').split | |
end | |
story = Story.new | |
story.do_story(input) | |
end | |
if __FILE__ == $0 | |
if false # debugging | |
main(['Minimal { } do: status']) | |
main(['Single { add crate } do: take crate']) | |
main(['Single { print ( Hello, world! ) } do: status']) | |
main(['Single { add crate open north to Single', | |
' if crate in pack then print ( found ) end }', | |
' do: take crate go north']) | |
#main(['Multiple { add crate open north to Single', | |
# ' if womp in pack then print ( womp ) end }', | |
# ' do: take womp go north status inventory']) | |
# expected output: | |
# > status | |
# Status for room Minimal: | |
# The room is empty. | |
# No exits. | |
# The crate is on the ground. | |
# > take crate | |
# You pick up the crate. | |
# hello, world! | |
# > status | |
# Status for room Single: | |
# The room is empty. | |
# No exits. | |
# The crate is on the ground. | |
# A door to the north opens. | |
# > take crate | |
# You pick up the crate. | |
# > go north | |
# found | |
else | |
main | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment