1
2 """ This module implements the GameContext class """
3 from gameobject import GameObject
4
5
6 -class GameContext(GameObject):
7 """
8 A GameContext corresponds to a geographic location and is a collection
9 of GameObjects, GameActors and state attributes. They exist in
10 higherarchical relationships (e.g. kingdom, village, buiding, room).
11 """
12
13 - def __init__(self, name="context", descr=None, parent=None):
14 """
15 create a new GameObject
16 @param name: display name of this object
17 @param descr: human description of this object
18 """
19 super(GameContext, self).__init__(name, descr)
20 self.parent = parent
21 self.party = []
22 self.npcs = []
23
24 - def get(self, attribute):
25 """
26 return the value of an attribute
27
28 Differs from base class because calls flow up the chain of
29 parents if this instance does not have the requested attribute.
30
31 @param attribute: name of attribute to be fetched
32 @return: (string) value (or None)
33 """
34 if attribute in self.attributes:
35 return self.attributes[attribute]
36 elif self.parent is not None:
37 return self.parent.get(attribute)
38 return None
39
40 - def possible_actions(self, actor, context):
41 """
42 return a list of possible actions in this context
43
44 This base class merely passes that list up to our parent.
45
46 @param actor: GameActor initiating the action
47 @param context: GameContext for this action (should be "self")
48 @return: list of possible GameActions
49 """
50
51 actions = super(GameContext, self).possible_actions(actor, context)
52 return actions
53
54 - def accept_action(self, action, actor, context):
55 """
56 receive and process the effects of an action
57
58 The only verb supported by this base class is SEARCH, which it passes
59 on to any hidden (RESISTANCE.SEARCH > 0) object in this context.
60
61 @param action: GameAction being performed
62 @param actor: GameActor initiating the action
63 @param context: GameContext in which the action is happening
64
65 @return: (boolean success, string description of the effect)
66 """
67
68 if action.verb == "SEARCH":
69 found_stuff = False
70 result = ""
71
72 for thing in self.objects:
73 concealment = thing.get("RESISTANCE.SEARCH")
74 if concealment is not None and concealment > 0:
75
76 (success, descr) = thing.accept_action(action, actor,
77 context)
78 if success:
79 found_stuff = True
80 if result == "":
81 result = descr
82 else:
83 result += "\n " + descr
84 return(found_stuff, result)
85
86
87 return super(GameContext, self).accept_action(action,
88 actor, context)
89
90 - def get_party(self):
91 """
92 @return: list of player GameActors in this context
93 """
94 return self.party
95
96 - def add_member(self, member):
97 """
98 Add an player character to this context
99 @param member: (GameActor) player to be added
100 """
101 if member not in self.party:
102 self.party.append(member)
103
104 - def remove_member(self, member):
105 """
106 Remove a player character from this context
107 @param member: (GameActor) player to be removed
108 """
109 if member in self.party:
110 self.party.remove(member)
111
112 - def get_npcs(self):
113 """
114 return a list of the NPCs GameActors in this context
115 """
116 return self.npcs
117
118 - def add_npc(self, npc):
119 """
120 Add an NPC to this context
121 @param npc: (GameActor) the NPC to be added
122 """
123 if npc not in self.npcs:
124 self.npcs.append(npc)
125
126 - def remove_npc(self, npc):
127 """
128 Remove a non-player character from this context
129 @param npc: (GameActor) NPC to be removed
130 """
131 if npc in self.npcs:
132 self.npcs.remove(npc)
133
134
135
136
138 """
139 exercise {add,remove}_member and get_party
140 - new & empty
141 - add one, add a second
142 - delete the first, add a third
143 - delete the third, delete the second
144 """
145 a_1 = GameObject("a1")
146 a_2 = GameObject("a2")
147 a_3 = GameObject("a3")
148
149 tried = 0
150 passed = 0
151
152 print("creating a new GameContext and confirming no party")
153 cxt = GameContext()
154 party = cxt.get_party()
155 tried += 1
156 assert not party, "new GameContext is not empty"
157 passed += 1
158
159 print("adding a first member and confirming party of one")
160 cxt.add_member(a_1)
161 party = cxt.get_party()
162 tried += 2
163 assert len(party) == 1, "after adding first party member, len != 1"
164 assert a_1 in party,\
165 "after adding first party member, he is not in the party"
166 passed += 2
167
168 print("adding a second member and confirming party of two")
169 cxt.add_member(a_2)
170 party = cxt.get_party()
171 tried += 3
172 assert len(party) == 2, "after adding second party member, len != 2"
173 assert a_1 in party,\
174 "after adding second party member, first is no longer there"
175 assert a_2 in party,\
176 "after adding second party member, he is not there"
177 passed += 3
178
179 print("removing first member and confirming party of one")
180 cxt.remove_member(a_1)
181 party = cxt.get_party()
182 tried += 3
183 assert len(party) == 1, "after removing first party member, len != 1"
184 assert a_1 not in party,\
185 "after removing first party member, he is still there"
186 assert a_2 in party,\
187 "after removing first party member, second is no longer there"
188 passed += 3
189
190 print("adding a third member and confirming party of two")
191 cxt.add_member(a_3)
192 party = cxt.get_party()
193 tried += 3
194 assert len(party) == 2, "after adding another party member, len != 2"
195 assert a_2 in party,\
196 "after adding another party member, previous is no longer there"
197 assert a_3 in party, "after adding another party member, he is not there"
198 passed += 3
199
200 print("removing third member and confirming party of one")
201 cxt.remove_member(a_3)
202 party = cxt.get_party()
203 tried += 3
204 assert len(party) == 1, "after removing another party member, len != 1"
205 assert a_3 not in party,\
206 "after removing another party member, he is still there"
207 assert a_2 in party,\
208 "after removing another party member, second is no longer there"
209 passed += 3
210
211 print("removing final member and confirming no party")
212 cxt.remove_member(a_2)
213 party = cxt.get_party()
214 tried += 2
215 assert not party, "after removing final party member, len != 0"
216 assert a_2 not in party,\
217 "after removing final party member, he is still there"
218 passed += 2
219
220 print()
221 return (tried, passed)
222
223
224
226 """
227 exercise {add,remove}_npc and get_npcs
228 - new & empty
229 - add one, add a second
230 - delete the first, add a third
231 - delete the third, delete the second
232 """
233 a_1 = GameObject("a1")
234 a_2 = GameObject("a2")
235 a_3 = GameObject("a3")
236
237 tried = 0
238 passed = 0
239
240 print("creating a new GameContext and confirming no NPCs")
241 cxt = GameContext()
242 party = cxt.get_npcs()
243 tried += 1
244 assert not party, \
245 "new GameContext is not empty"
246 passed += 1
247
248 print("adding first NPC and confirming one NPC")
249 cxt.add_npc(a_1)
250 party = cxt.get_npcs()
251 tried += 2
252 assert len(party) == 1, \
253 "after adding first NPC, len != 1"
254 assert a_1 in party, \
255 "after adding first NPC, he is not in the party"
256 passed += 2
257
258 print("adding second NPC and confirming two NPCs")
259 cxt.add_npc(a_2)
260 party = cxt.get_npcs()
261 tried += 3
262 assert len(party) == 2, \
263 "after adding second NPC, len != 2"
264 assert a_1 in party, \
265 "after adding second NPC, first is no longer there"
266 assert a_2 in party, \
267 "after adding second NPC, he is not there"
268 passed += 3
269
270 print("removing first NPC and confirming one NPC")
271 cxt.remove_npc(a_1)
272 party = cxt.get_npcs()
273 tried += 3
274 assert len(party) == 1, \
275 "after removing first NPC, len != 1"
276 assert a_1 not in party, \
277 "after removing first NPC, he is still there"
278 assert a_2 in party, \
279 "after removing first NPC, second is no longer there"
280 passed += 3
281
282 print("adding third NPC and confirming two NPCs")
283 cxt.add_npc(a_3)
284 party = cxt.get_npcs()
285 tried += 3
286 assert len(party) == 2, \
287 "after adding another NPC, len != 2"
288 assert a_2 in party, \
289 "after adding another NPC, previous is no longer there"
290 assert a_3 in party, \
291 "after adding another NPC, he is not there"
292 passed += 3
293
294 print("removing third NPC and confirming one NPC")
295 cxt.remove_npc(a_3)
296 party = cxt.get_npcs()
297 tried += 3
298 assert len(party) == 1, \
299 "after removing another NPC, len != 1"
300 assert a_3 not in party, \
301 "after removing another NPC, he is still there"
302 assert a_2 in party, \
303 "after removing another NPC, second is no longer there"
304 passed += 3
305
306 print("removing final NPC and confirming no NPCs")
307 cxt.remove_npc(a_2)
308 party = cxt.get_npcs()
309 tried += 2
310 assert not party, \
311 "after removing final NPC, len != 0"
312 assert a_2 not in party, \
313 "after removing final NPC, he is still there"
314 passed += 2
315
316 print()
317 return (tried, passed)
318
319
321 """
322 exercise hierarchical gets
323
324 set distinct and overlapping properties at various levels of nested
325 GameContexts, and see which values are returned from gets in
326 different levels:
327 - each level has a different value
328 - different levels define different properties
329 - a property in no level will return None
330
331 """
332 tried = 0
333 passed = 0
334
335 c_1 = GameContext("C1")
336 c_2 = GameContext("C2", parent=c_1)
337 c_3 = GameContext("C3", parent=c_2)
338
339 print("gets are satisfied from nearest context")
340 c_1.set("CAME_FROM", 1)
341 c_2.set("CAME_FROM", 2)
342 c_3.set("CAME_FROM", 3)
343
344 ret = c_1.get("CAME_FROM")
345 tried += 1
346 assert ret == 1, \
347 "grand-parent.get() returns " + str(ret) + "!=1"
348 passed += 1
349 ret = c_2.get("CAME_FROM")
350 tried += 1
351 assert ret == 2, \
352 "parent.get() returns " + str(ret) + "!=2"
353 passed += 1
354 ret = c_3.get("CAME_FROM")
355 tried += 1
356 assert ret == 3, \
357 "child.get() returns " + str(ret) + "!=1"
358 passed += 1
359
360 print("gets follow parents if necessary")
361 c_3.set("IN_THREE", 3)
362 ret = c_3.get("IN_THREE")
363 tried += 1
364 assert ret == 3, \
365 "child.get() does not return its own value"
366 passed += 1
367 c_2.set("IN_TWO", 2)
368 ret = c_3.get("IN_TWO")
369 tried += 1
370 assert ret == 2, \
371 "child.get() does not return parent's value"
372 passed += 1
373 c_1.set("IN_ONE", 1)
374 ret = c_3.get("IN_ONE")
375 tried += 1
376 assert ret == 1, \
377 "child.get() does not return grand-parents value"
378 passed += 1
379
380 print("absent values reported as None")
381 ret = c_3.get("NOWHERE")
382 tried += 1
383 assert ret is None, \
384 "get() of non-existent value returns " + str(ret)
385 passed += 1
386
387 print()
388 return (tried, passed)
389
390
392 """
393 A TestObject is a hidden object that will be found after a
394 specified number of searches.
395 """
397 """
398 a TestObject will be hidden for a specified number of searches,
399 after which it will be found
400
401 @param name: (string) name of the object
402 @param searches: (int) number of searches to find it
403 """
404 super(TestObject, self).__init__(name, "findable object")
405 self.set("RESISTANCE.SEARCH", searches)
406 self.set("SEARCHES", 0)
407
409 """
410 receive and process the effects of an action
411
412 The only verb supported by this test class is SEARCH,
413 which will succeed after a specified number of tries
414
415 @param action: GameAction being performed
416 @param actor: GameActor initiating the action
417 @param context: GameContext in which the action is happening
418
419 @return: (boolean success, string description of the effect)
420 """
421 if action.verb != "SEARCH":
422 return (False, "Unsupported action")
423
424 resistance = self.get("RESISTANCE.SEARCH")
425 searches = self.get("SEARCHES") + 1
426 self.set("SEARCHES", searches)
427
428 if searches >= resistance:
429 return (True, self.name)
430 return (False, "!" + self.name)
431
432
434 """
435 exercise search for hidden objects
436 - create a context with one non-hidden and three hidden test objects
437 - the test objects will be discovered after 1, 2, and 3 searches
438 - do three searches and confirm the right objects found by each
439 """
440 cxt = GameContext("searchable")
441
442
443 o_1 = TestObject("one", 1)
444 o_1.set("RESISTANCE.SEARCH", 1)
445 cxt.add_object(o_1)
446 o_2 = TestObject("two", 2)
447 o_2.set("RESISTANCE.SEARCH", 2)
448 cxt.add_object(o_2)
449 o_3 = TestObject("three", 3)
450 o_3.set("RESISTANCE.SEARCH", 3)
451 cxt.add_object(o_3)
452
453
454 search = GameObject("SEARCH OPERATION")
455 search.verb = "SEARCH"
456
457 tried = 0
458 passed = 0
459
460 print("first search for (singly hidden object) " + o_1.name)
461 (success, descr) = cxt.accept_action(search, None, cxt)
462 tried += 2
463 assert success, "first SEARCH failed"
464 found = descr.replace('\n', ' ').split()
465 assert found == ['one', '!two', '!three'], \
466 "first search returned " + descr
467 passed += 2
468
469 print("second search for (doubly hidden object) " + o_2.name)
470 (success, descr) = cxt.accept_action(search, None, cxt)
471 tried += 2
472 assert success, "second SEARCH failed"
473 found = descr.replace('\n', ' ').split()
474 assert found == ['one', 'two', '!three'], \
475 "first search returned " + descr
476 passed += 2
477
478 print("third search for (tripply hidden object) " + o_3.name)
479 (success, descr) = cxt.accept_action(search, None, cxt)
480 tried += 2
481 assert success, "third SEARCH failed"
482 found = descr.replace('\n', ' ').split()
483 assert found == ['one', 'two', 'three'], \
484 "third search returned " + descr
485 passed += 2
486
487 print()
488
489 return (tried, passed)
490
491
493 """
494 Run all unit-test cases and print out summary of results
495 """
496 (t_1, p_1) = member_tests()
497 (t_2, p_2) = npc_tests()
498 (t_3, p_3) = get_tests()
499 (t_4, p_4) = search_tests()
500 tried = t_1 + t_2 + t_3 + t_4
501 passed = p_1 + p_2 + p_3 + p_4
502 if tried == passed:
503 print("Passed all {} GameContext tests".format(passed))
504 else:
505 print("FAILED {}/{} GameContext tests".format(tried-passed, tried))
506
507
508 if __name__ == "__main__":
509 main()
510