-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdungeon.rb
229 lines (190 loc) · 5.8 KB
/
dungeon.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#!/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
#
# uncomment following require to enable debugging
# - see http://www.uwplatt.edu/csse/tools/ruby/ruby-debug.html
#require 'ruby-debug'
# Captures state of the dungeon
class Dungeon
attr_reader :rooms # type: list(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