git @ Cat's Eye Technologies The-Swallows / fc39e55
Split engine and story into two sub-packages. Cat's Eye Technologies 11 years ago
11 changed file(s) with 1143 addition(s) and 1143 deletion(s). Raw diff Collapse all Expand all
1111 sys.path.insert(0, join(dirname(realpath(sys.argv[0])), '..', 'src'))
1212
1313 # now we can:
14 from swallows.events import LegacyPublisher
15 from swallows.objects import Location, ProperLocation, Male, Female
14 from swallows.engine.events import LegacyPublisher
15 from swallows.engine.objects import Location, ProperLocation, Male, Female
1616
1717 ### world ###
1818
1010 sys.path.insert(0, join(dirname(realpath(sys.argv[0])), '..', 'src'))
1111
1212 # now we can:
13 from swallows.events import LegacyPublisher
14 from swallows.objects import Male
13 from swallows.engine.events import LegacyPublisher
14 from swallows.engine.objects import Male
1515
1616 # the following is not good. but it works.
1717 # better options would be:
1818 # - define the world-specific behaviour of the characters in swallows.world
1919 # - (less better) have alice & bob take these objects as dependency injections
20 import swallows.objects
21 import swallows.world
22 swallows.objects.revolver = swallows.world.revolver
23 swallows.objects.brandy = swallows.world.brandy
24 swallows.objects.dead_body = swallows.world.dead_body
20 import swallows.engine.objects
21 import swallows.story.world
22 swallows.engine.objects.revolver = swallows.story.world.revolver
23 swallows.engine.objects.brandy = swallows.story.world.brandy
24 swallows.engine.objects.dead_body = swallows.story.world.dead_body
2525
2626 # we extend it by adding a new character
2727 fred = Male('Fred')
3030
3131 publisher = LegacyPublisher(
3232 characters=(
33 swallows.world.alice,
34 swallows.world.bob,
33 swallows.story.world.alice,
34 swallows.story.world.bob,
3535 fred
3636 ),
37 setting=swallows.world.house,
37 setting=swallows.story.world.house,
3838 title="My _The Swallows_ Fanfic",
3939 #debug=True,
4040 )
1010 # get the ../src/ directory onto the Python module search path
1111 sys.path.insert(0, join(dirname(realpath(sys.argv[0])), '..', 'src'))
1212
13 # now we can:
14 from swallows.events import LegacyPublisher
13 # now we can import things, like:
14 from swallows.engine.events import LegacyPublisher
1515
1616 # the following is not good. but it works.
1717 # better options would be:
1818 # - define the world-specific behaviour of the characters in swallows.world
1919 # - (less better) have alice & bob take these objects as dependency injections
20 import swallows.objects
21 import swallows.world
22 swallows.objects.revolver = swallows.world.revolver
23 swallows.objects.brandy = swallows.world.brandy
24 swallows.objects.dead_body = swallows.world.dead_body
20 import swallows.engine.objects
21 import swallows.story.world
22 swallows.engine.objects.revolver = swallows.story.world.revolver
23 swallows.engine.objects.brandy = swallows.story.world.brandy
24 swallows.engine.objects.dead_body = swallows.story.world.dead_body
2525
2626 ### main ###
2727
2828 publisher = LegacyPublisher(
2929 characters=(
30 swallows.world.alice,
31 swallows.world.bob,
30 swallows.story.world.alice,
31 swallows.story.world.bob,
3232 ),
33 setting=swallows.world.house,
33 setting=swallows.story.world.house,
3434 title="Title TBD (Book Four of _The Swallows_ series)",
3535 #debug=True,
3636 )
0 #!/usr/bin/env python
1
2 import random
3 import sys
4
5 from swallows.util import pick
6
7 # TODO
8
9 # Diction:
10 # the event-accumulation framework could use rewriting at some point.
11 # eliminate identical duplicate sentences
12 # Bob is in the dining room & "Bob made his way to the dining room" ->
13 # "Bob wandered around for a bit, then came back to the dining room"
14 # a better solution for "Bob was in the kitchen" at the start of a paragraph;
15 # this might include significant memories Bob acquired in the last
16 # paragraph -- such as finding a revolver in the bed
17 # paragraphs should not always be the same number of events. variety!
18 # the Editor should take all the events in the chapter, and decide where
19 # paragraph breaks should go. this is difficult, because syncing up
20 # Bob's and Alice's events. timestamps?
21 # at least, the Editor should record "rich events", which include information
22 # about the main (acting) character, and where the audience last saw them
23 # use indef art when they have no memory of an item that they see
24 # dramatic irony would be really nice, but hard to pull off. Well, a certain
25 # amount happens naturally now, with character pov. but more could be done
26 # "Chapter 3. _In which Bob hides the stolen jewels in the mailbox, etc_" --
27 # i.e. chapter summaries -- that's a little too fancy to hope for, but with
28 # a sufficiently smart Editor it could be done
29
30 ### EVENTS ###
31
32 class Event(object):
33 def __init__(self, phrase, participants, excl=False):
34 """participants[0] is always the initiator, and we
35 record the location that the event was initiated in.
36
37 For now, we assume such an event can be:
38 - observed by every actor at that location
39 - affects only actors at that location
40
41 In the future, we *may* have:
42 - active and passive participants
43 - active participants all must be present at the location
44 - passive participants need not be
45
46 """
47 self.phrase = phrase
48 self.participants = participants
49 self.location = participants[0].location
50 self.excl = excl
51
52 def initiator(self):
53 return self.participants[0]
54
55 def __str__(self):
56 phrase = self.phrase
57 i = 0
58 for participant in self.participants:
59 phrase = phrase.replace('<%d>' % (i + 1), participant.render(self.participants))
60 phrase = phrase.replace('<indef-%d>' % (i + 1), participant.indefinite())
61 phrase = phrase.replace('<his-%d>' % (i + 1), participant.posessive())
62 phrase = phrase.replace('<him-%d>' % (i + 1), participant.accusative())
63 phrase = phrase.replace('<he-%d>' % (i + 1), participant.pronoun())
64 phrase = phrase.replace('<was-%d>' % (i + 1), participant.was())
65 phrase = phrase.replace('<is-%d>' % (i + 1), participant.is_())
66 i = i + 1
67 if self.excl:
68 phrase = phrase + '!'
69 else:
70 phrase = phrase + '.'
71 return phrase[0].upper() + phrase[1:]
72
73
74 class EventCollector(object):
75 def __init__(self):
76 self.events = []
77
78 def collect(self, event):
79 self.events.append(event)
80
81
82 class Oblivion(EventCollector):
83 def collect(self, event):
84 pass
85
86
87 oblivion = Oblivion()
88
89
90 # 'diction engine' -- almost exactly like a peephole optimizer -- convert
91 # "Bob went to the shed. Bob saw Alice." into
92 # "Bob went to the shed, where he saw Alice."
93 # btw, we currently get a new editor for every paragraph
94 class LegacyEditor(object):
95 """The Editor is remarkably similar to the _peephole optimizer_ in
96 compiler construction. Instead of replacing sequences of instructions
97 with more efficient but semantically equivalent sequences of
98 instructions, it replaces sequences of sentences with more readable
99 but semantically equivalent sequences of sentences.
100
101 """
102 MEMORY = 1
103
104 def __init__(self):
105 self.character = None
106 self.character_location = {}
107 self.events = []
108
109 def read(self, event):
110 if len(self.events) < self.__class__.MEMORY:
111 self.events.append(event)
112 return
113
114 character = event.participants[0]
115 # update our idea of their location
116 self.character_location[character.name] = character.location
117 # todo: check our idea of their location vs where they are,
118 # but that won't matter until an editor looks at more than one
119 # paragraph anyway
120
121 if character == self.character: # same character doing stuff
122 if event.phrase.startswith('<1>'):
123 event.phrase = '<he-1>' + event.phrase[3:]
124
125 if (self.events[-1].phrase == '<1> made <his-1> way to <2>' and
126 event.phrase == '<1> went to <2>'):
127 self.events[-1].participants[1] = event.participants[1]
128 elif (self.events[-1].phrase == '<1> went to <2>' and
129 event.phrase == '<1> went to <2>'):
130 self.events[-1].phrase = '<1> made <his-1> way to <2>'
131 self.events[-1].participants[1] = event.participants[1]
132 elif (self.events[-1].phrase == '<he-1> made <his-1> way to <2>' and
133 event.phrase == '<he-1> went to <2>'):
134 self.events[-1].participants[1] = event.participants[1]
135 elif (self.events[-1].phrase == '<he-1> went to <2>' and
136 event.phrase == '<he-1> went to <2>'):
137 self.events[-1].phrase = '<he-1> made <his-1> way to <2>'
138 self.events[-1].participants[1] = event.participants[1]
139 else:
140 self.events.append(event)
141 else: # new character doing stuff
142 self.character = character
143 self.events.append(event)
144
145
146 class LegacyPublisher(object):
147 """Publisher which uses the old Event/Editor framework.
148
149 Will probably go away eventually, but nice to have as a reference
150 while working on the new Event/Editor framework.
151
152 """
153 def __init__(self, **kwargs):
154 self.characters = kwargs.get('characters')
155 self.setting = kwargs.get('setting')
156 self.friffery = kwargs.get('friffery', False)
157 self.debug = kwargs.get('debug', False)
158 self.title = kwargs.get('title', "Untitled")
159 self.chapters = kwargs.get('chapters', 16)
160 self.paragraphs_per_chapter = kwargs.get('paragraphs_per_chapter', 25)
161
162 def publish(self):
163 print self.title
164 print "=" * len(self.title)
165 print
166
167 for chapter in range(1, self.chapters+1):
168 print "Chapter %d." % chapter
169 print "-----------"
170 print
171
172 for character in self.characters:
173 # don't continue a conversation from the previous chapter, please
174 character.topic = None
175 character.location = None
176
177 for paragraph in range(1, self.paragraphs_per_chapter+1):
178 for character in self.characters:
179 character.collector = EventCollector()
180
181 # we alternate pov like so:
182 pov_actor = (self.characters)[(paragraph - 1) % len(self.characters)]
183
184 for actor in self.characters:
185 if actor.location is None:
186 actor.place_in(pick(self.setting))
187 else:
188 # this is hacky & won't work for >2 characters:
189 if self.characters[0].location is not self.characters[1].location:
190 actor.emit("<1> was in <2>", [actor, actor.location])
191
192 while len(pov_actor.collector.events) < 20:
193 for actor in self.characters:
194 actor.live()
195
196 if self.friffery:
197 if paragraph == 1:
198 choice = random.randint(0, 3)
199 if choice == 0:
200 sys.stdout.write("It was raining. ")
201 if choice == 1:
202 sys.stdout.write("It was snowing. ")
203 if choice == 2:
204 sys.stdout.write("The sun was shining. ")
205 if choice == 3:
206 sys.stdout.write("The day was overcast and humid. ")
207 elif not str(c.events[0]).startswith("'"):
208 choice = random.randint(0, 8)
209 if choice == 0:
210 sys.stdout.write("Later on, ")
211 if choice == 1:
212 sys.stdout.write("Suddenly, ")
213 if choice == 2:
214 sys.stdout.write("After a moment's consideration, ")
215 if choice == 3:
216 sys.stdout.write("Feeling anxious, ")
217
218 if self.debug:
219 for character in self.characters:
220 print "%s'S POV:" % character.name.upper()
221 for event in character.collector.events:
222 print str(event)
223 print
224 character.dump_memory()
225 print
226 print "- - - - -"
227 print
228
229 if not self.debug:
230 editor = LegacyEditor()
231 for event in pov_actor.collector.events:
232 editor.read(event)
233 for event in editor.events:
234 sys.stdout.write(str(event) + " ")
235 #sys.stdout.write("\n")
236 print
237 print
0 #!/usr/bin/env python
1
2 import random
3 import sys
4
5 from swallows.engine.events import Event
6 from swallows.util import pick
7
8 # TODO
9
10 # they check containers while someone else is in the room? how'd that get that way?
11 # 'Hello, Alice', said Bob. 'Hello, Bob', replied Alice. NEVER GETS OLD
12 # they should always scream at seeing the dead body. the scream should
13 # be heard throughout the house and yard.
14 # ...they check that the brandy is still in the liquor cabinet. is this
15 # really necessary?
16 # certain things can't be taken, but can be dragged (like the body)
17 # path-finder between any two rooms -- not too difficult, even if it
18 # would be nicer in Prolog.
19 # "it was so nice" -- actually *have* memories of locations, and feelings
20 # (good/bad, 0 to 10 or something) about memories
21 # anxiety memory = the one they're most recently panicked about
22 # memory of whether the revolver was loaded last time they saw it
23 # calling their bluff
24 # making a run for it when at gunpoint (or trying to distract them,
25 # slap the gun away, scramble for it, etc)
26 # revolver might jam when they try to shoot it (maybe it should be a
27 # pistol instead, as those can jam more easily)
28 # dear me, someone might actually get shot. then what? another dead body?
29
30 ### TOPICS ###
31
32 # a "topic" is just what a character has recently had addressed to
33 # them. It could be anything, not just words, by another character
34 # (for example, a gesture.)
35
36 class Topic(object):
37 def __init__(self, originator, subject=None):
38 self.originator = originator
39 self.subject = subject
40
41
42 class GreetTopic(Topic):
43 pass
44
45
46 class SpeechTopic(Topic):
47 pass
48
49
50 class QuestionTopic(Topic):
51 pass
52
53
54 class WhereQuestionTopic(Topic):
55 pass
56
57
58 class ThreatGiveMeTopic(Topic):
59 pass
60
61
62 class ThreatTellMeTopic(Topic):
63 pass
64
65
66 class ThreatAgreeTopic(Topic):
67 pass
68
69
70 ### MEMORIES ###
71
72 class Memory(object):
73 def __init__(self, subject, location, i_hid_it_there=False):
74 self.subject = subject # the thing being remembered
75 self.location = location # where we last remember seeing it
76 self.i_hid_it_there = i_hid_it_there
77
78
79 ### ACTORS (objects in the world) ###
80
81 class Actor(object):
82 def __init__(self, name, location=None, collector=None):
83 self.name = name
84 self.collector = collector
85 self.contents = []
86 self.enter = ""
87 self.location = None
88 if location is not None:
89 self.move_to(location)
90
91 def notable(self):
92 return self.treasure() or self.weapon() or self.animate() or self.horror()
93
94 def treasure(self):
95 return False
96
97 def weapon(self):
98 return False
99
100 def horror(self):
101 return False
102
103 def takeable(self):
104 return False
105
106 def animate(self):
107 return False
108
109 def container(self):
110 return False
111
112 def article(self):
113 return 'the'
114
115 def posessive(self):
116 return "its"
117
118 def accusative(self):
119 return "it"
120
121 def pronoun(self):
122 return "it"
123
124 def was(self):
125 return "was"
126
127 def is_(self):
128 return "is"
129
130 def emit(self, *args, **kwargs):
131 if self.collector:
132 self.collector.collect(Event(*args, **kwargs))
133
134 def move_to(self, location):
135 if self.location:
136 self.location.contents.remove(self)
137 self.location = location
138 self.location.contents.append(self)
139
140 def render(self, participants):
141 name = self.name
142 if participants:
143 subject = participants[0]
144 posessive = subject.name + "'s"
145 name = name.replace(posessive, subject.posessive())
146 article = self.article()
147 if not article:
148 return name
149 return '%s %s' % (article, name)
150
151 def indefinite(self):
152 article = 'a'
153 if self.name.startswith(('a', 'e', 'i', 'o', 'u')):
154 article = 'an'
155 return '%s %s' % (article, self.name)
156
157 # for debugging
158 def dump_memory(self):
159 for thing in self.memories:
160 memory = self.memories[thing]
161 print ".oO{ %s is in %s }" % (
162 memory.subject.render([]),
163 memory.location.render([]))
164 if memory.i_hid_it_there:
165 print ".oO{ I hid it there }"
166 print "desired items:", repr(self.desired_items)
167 print "decisions:", repr(self.what_to_do_about)
168 print "knowledge of others' decisions:", repr(self.other_decision_about)
169
170
171 ### unfortunate externals ###
172
173 # items that the mechanics need to know about; they are defined "for reals"
174 # in swallows.world, which may not even be used, so for now we make stand-in
175 # dummy objects for them
176 revolver = Actor('non-existent revolver')
177 brandy = Actor('non-existent brandy')
178 dead_body = Actor('non-existent dead body')
179
180
181 ### some mixins for Actors ###
182
183 class ProperMixin(object):
184 def article(self):
185 return ''
186
187
188 class PluralMixin(object):
189 def posessive(self):
190 return "their"
191
192 def accusative(self):
193 return "them"
194
195 def pronoun(self):
196 return "they"
197
198 def indefinite(self):
199 article = 'some'
200 return '%s %s' % (article, self.name)
201
202 def was(self):
203 return "were"
204
205 def is_(self):
206 return "are"
207
208
209 class MasculineMixin(object):
210 def posessive(self):
211 return "his"
212
213 def accusative(self):
214 return "him"
215
216 def pronoun(self):
217 return "he"
218
219
220 class FeminineMixin(object):
221 def posessive(self):
222 return "her"
223
224 def accusative(self):
225 return "her"
226
227 def pronoun(self):
228 return "she"
229
230
231 ### ANIMATE OBJECTS ###
232
233 class Animate(Actor):
234 def __init__(self, name, location=None, collector=None):
235 Actor.__init__(self, name, location=location, collector=None)
236 self.topic = None
237 # hash of actor object to Memory object
238 self.memories = {}
239 self.desired_items = set()
240 # this should really be *derived* from having a recent memory
241 # of seeing a dead body in the bathroom. but for now,
242 self.nerves = 'calm'
243 # this, too, should be more sophisticated.
244 # it is neither a memory, nor a belief, but a judgment, and
245 # eventually possibly a goal.
246 # hash maps Actors to strings
247 self.what_to_do_about = {}
248 self.other_decision_about = {}
249
250 def animate(self):
251 return True
252
253 def remember(self, thing, location, i_hid_it_there=False):
254 assert isinstance(thing, Actor)
255 self.memories[thing] = Memory(thing, location, i_hid_it_there=i_hid_it_there)
256
257 def recall(self, thing):
258 assert isinstance(thing, Actor)
259 return self.memories.get(thing, None)
260
261 def address(self, other, topic, phrase, participants=None):
262 if participants is None:
263 participants = [self, other]
264 other.topic = topic
265 # in the absence of a better event-collection system
266 # we do this sort of thing when >1 actor can observe an event:
267 self.emit(phrase, participants)
268 other.emit(phrase, participants)
269
270 def greet(self, other, phrase, participants=None):
271 self.address(other, GreetTopic(self), phrase, participants)
272
273 def speak_to(self, other, phrase, participants=None, subject=None):
274 self.address(other, SpeechTopic(self, subject=subject), phrase, participants)
275
276 def question(self, other, phrase, participants=None, subject=None):
277 self.address(other, QuestionTopic(self, subject=subject), phrase, participants)
278
279 def place_in(self, location):
280 # like move_to but quieter; for setting up scenes etc
281 if self.location is not None:
282 self.location.contents.remove(self)
283 self.location = location
284 self.location.contents.append(self)
285 self.emit("<1> <was-1> in <2>", [self, self.location])
286 for x in self.location.contents:
287 if x == self:
288 continue
289 if x.notable():
290 self.emit("<1> saw <2>", [self, x])
291 self.remember(x, self.location)
292
293 def move_to(self, location):
294 assert(location != self.location)
295 assert(location is not None)
296 for x in self.location.contents:
297 # otherwise we get "Bob saw Bob leave the room", eh?
298 if x is self:
299 continue
300 if x.animate():
301 x.emit("<1> saw <2> leave the %s" % x.location.noun(), [x, self])
302 if self.location is not None:
303 self.location.contents.remove(self)
304 self.location = location
305 self.location.contents.append(self)
306 self.emit("<1> went to <2>", [self, self.location])
307 if random.randint(0, 10) == 0:
308 self.emit("It was so nice being in <2> again",
309 [self, self.location], excl=True)
310
311 # okay, look around you.
312 for x in self.location.contents:
313 if x == self:
314 continue
315 if x.horror():
316 memory = self.recall(x)
317 if memory:
318 amount = pick(['shudder', 'wave'])
319 emotion = pick(['fear', 'disgust', 'sickness', 'loathing'])
320 self.emit("<1> felt a %s of %s as <he-1> looked at <2>" % (amount, emotion), [self, x])
321 self.remember(x, self.location)
322 else:
323 verb = pick(['screamed', 'yelped', 'went pale'])
324 self.emit("<1> %s at the sight of <indef-2>" % verb, [self, x], excl=True)
325 self.remember(x, self.location)
326 self.nerves = 'shaken'
327 elif x.animate():
328 other = x
329 self.emit("<1> saw <2>", [self, other])
330 other.emit("<1> saw <2> walk into the %s" % self.location.noun(), [other, self])
331 self.remember(x, self.location)
332 self.greet(x, "'Hello, <2>,' said <1>")
333 for y in other.contents:
334 if y.treasure():
335 self.emit(
336 "<1> noticed <2> <was-2> carrying <indef-3>",
337 [self, other, y])
338 if revolver.location == self:
339 self.point_at(other, revolver)
340 self.address(other,
341 ThreatGiveMeTopic(self, subject=y),
342 "'Please give me <3>, <2>, or I shall shoot you,' <he-1> said",
343 [self, other, y])
344 return
345 # another case of mind-reading. well, it helps the story advance!
346 # (it would help more to double-check this against your OWN memory)
347 if revolver.location == self:
348 for thing in other.memories:
349 memory = other.recall(thing)
350 self_memory = self.recall(thing)
351 if self_memory:
352 continue
353 if memory.i_hid_it_there and memory.subject is not revolver:
354 self.point_at(other, revolver)
355 self.address(other,
356 ThreatTellMeTopic(self, subject=thing),
357 "'Tell me where you have hidden <3>, <2>, or I shall shoot you,' <he-1> said",
358 [self, other, thing])
359 return
360 elif x.notable():
361 self.emit("<1> saw <2>", [self, x])
362 self.remember(x, self.location)
363
364 def point_at(self, other, item):
365 # it would be nice if there was some way to
366 # indicate the revolver as part of the Topic which will follow,
367 # or otherwise indicate the context as "at gunpoint"
368
369 # XXX SERIOUSLY WE HAVE TO FIX THIS
370 # assert self.location == other.location
371 assert item.location == self
372 self.emit("<1> pointed <3> at <2>",
373 [self, other, item])
374 other.emit("<1> pointed <3> at <2>",
375 [self, other, item])
376 other.remember(item, self)
377
378 def put_down(self, item):
379 assert(item.location == self)
380 self.emit("<1> put down <2>", [self, item])
381 item.move_to(self.location)
382 self.remember(item, self.location)
383 for other in self.location.contents:
384 if other is self:
385 continue
386 if other.animate():
387 other.emit("<1> put down <2>", [self, item])
388 other.remember(item, self.location)
389
390 def pick_up(self, item):
391 assert(item.location == self.location)
392 self.emit("<1> picked up <2>", [self, item])
393 item.move_to(self)
394 self.remember(item, self)
395 for other in self.location.contents:
396 if other is self:
397 continue
398 if other.animate():
399 other.emit("<1> picked up <2>", [self, item])
400 other.remember(item, self)
401
402 def give_to(self, other, item):
403 assert(item.location == self)
404 # XXX seriously? this isn't preserved? blast
405 # assert(self.location == other.location)
406 self.emit("<1> gave <3> to <2>", [self, other, item])
407 other.emit("<1> gave <3> to <2>", [self, other, item])
408 item.move_to(other)
409 self.remember(item, other)
410 other.remember(item, other)
411
412 def wander(self):
413 self.move_to(
414 self.location.exits[
415 random.randint(0, len(self.location.exits)-1)
416 ]
417 )
418
419 def live(self):
420 # first, if in a conversation, turn total attention to that
421 if self.topic is not None:
422 return self.converse(self.topic)
423
424 # otherwise, if there are items here that you desire, you *must* pick
425 # them up.
426 for x in self.location.contents:
427 if x.treasure() or x.weapon() or x in self.desired_items:
428 self.pick_up(x)
429 return
430 people_about = False
431
432 # otherwise, fixate on some valuable object (possibly the revolver)
433 # that you are carrying:
434 fixated_on = None
435 for y in self.contents:
436 if y.treasure():
437 fixated_on = y
438 break
439 if not fixated_on and random.randint(0, 20) == 0 and revolver.location == self:
440 fixated_on = revolver
441
442 # check if you are alone
443 for x in self.location.contents:
444 if x.animate() and x is not self:
445 people_about = True
446
447 choice = random.randint(0, 25)
448 if choice < 10 and not people_about:
449 return self.hide_and_seek(fixated_on)
450 if choice < 20:
451 return self.wander()
452 if choice == 20:
453 self.emit("<1> yawned", [self])
454 elif choice == 21:
455 self.emit("<1> gazed thoughtfully into the distance", [self])
456 elif choice == 22:
457 self.emit("<1> thought <he-1> heard something", [self])
458 elif choice == 23:
459 self.emit("<1> scratched <his-1> head", [self])
460 elif choice == 24:
461 self.emit("<1> immediately had a feeling something was amiss", [self])
462 else:
463 return self.wander()
464
465 def hide_and_seek(self, fixated_on):
466 # check for some place to hide the thing you're fixating on
467 containers = []
468 for x in self.location.contents:
469 if x.container():
470 # did I hide something here previously?
471 memories = []
472 for thing in self.memories:
473 memory = self.recall(thing)
474 if memory.location == x:
475 memories.append(memory)
476 containers.append((x, memories))
477 if not containers:
478 return self.wander()
479 # ok! we now have a list of containers, each of which has zero or
480 # more memories of things being in it.
481 if fixated_on:
482 (container, memories) = pick(containers)
483 self.emit("<1> hid <2> in <3>", [self, fixated_on, container])
484 fixated_on.move_to(container)
485 self.remember(fixated_on, container, i_hid_it_there=True)
486 return self.wander()
487 else:
488 # we're looking for treasure!
489 # todo: it would maybe be better to prioritize this selection
490 (container, memories) = pick(containers)
491 # sometimes, we don't care what we think we know about something
492 # (this lets us, for example, explore things in hopes of brandy)
493 if memories and random.randint(0, 3) == 0:
494 memories = None
495 if memories:
496 memory = pick(memories)
497 picking_up = random.randint(0, 5) == 0
498 if memory.subject is revolver:
499 picking_up = True
500 if picking_up:
501 if memory.i_hid_it_there:
502 self.emit("<1> retrieved <3> <he-1> had hidden in <2>",
503 [self, container, memory.subject])
504 else:
505 self.emit("<1> retrieved <3> from <2>",
506 [self, container, memory.subject])
507 # but!
508 if memory.subject.location != container:
509 self.emit("But <he-2> <was-2> missing", [self, memory.subject], excl=True)
510 # forget ALLLLLLL about it, then. so realistic!
511 del self.memories[memory.subject]
512 else:
513 memory.subject.move_to(self)
514 self.remember(memory.subject, self)
515 else:
516 self.emit("<1> checked that <3> <was-3> still in <2>",
517 [self, container, memory.subject])
518 # but!
519 if memory.subject.location != container:
520 self.emit("But <he-2> <was-2> missing", [self, memory.subject], excl=True)
521 del self.memories[memory.subject]
522 else: # no memories of this
523 self.emit("<1> searched <2>", [self, container])
524 desired_things = []
525 for thing in container.contents:
526 # remember what you saw whilst searching this container
527 self.remember(thing, container)
528 if thing.treasure() or thing.weapon() or thing in self.desired_items:
529 desired_things.append(thing)
530 if desired_things:
531 thing = pick(desired_things)
532 self.emit("<1> found <2> there, and took <him-2>", [self, thing])
533 thing.move_to(self)
534 self.remember(thing, self)
535
536 def converse(self, topic):
537 self.topic = None
538 other = topic.originator
539 if isinstance(topic, ThreatGiveMeTopic):
540 found_object = None
541 for x in self.contents:
542 if x is topic.subject:
543 found_object = x
544 break
545 if not found_object:
546 self.speak_to(other,
547 "'But I don't have <3>!' protested <1>",
548 [self, other, topic.subject])
549 else:
550 self.speak_to(other,
551 "'Please don't shoot!', <1> cried",
552 [self, other, found_object])
553 self.give_to(other, found_object)
554 elif isinstance(topic, ThreatTellMeTopic):
555 memory = self.recall(topic.subject)
556 if not memory:
557 self.speak_to(other,
558 "'I have no memory of that, <2>,' <1> replied",
559 [self, other, topic.subject])
560 else:
561 self.speak_to(other,
562 "'Please don't shoot!', <1> cried, '<he-3> <is-3> in <4>'",
563 [self, other, topic.subject, memory.location])
564 # this is not really a *memory*, btw, it's a *belief*
565 other.remember(topic.subject, memory.location)
566 elif isinstance(topic, ThreatAgreeTopic):
567 decision = self.what_to_do_about[topic.subject]
568 self.speak_to(other,
569 "'You make a persuasive case for remaining undecided, <2>,' said <1>",
570 [self, other])
571 del self.what_to_do_about[topic.subject]
572 del other.other_decision_about[topic.subject]
573 elif isinstance(topic, GreetTopic):
574 # emit, because making this a speak_to leads to too much silliness
575 self.emit("'Hello, <2>,' replied <1>", [self, other])
576 # but otoh this sort of thing does not scale:
577 other.emit("'Hello, <2>,' replied <1>", [self, other])
578 # this needs to be more general
579 self_memory = self.recall(dead_body)
580 if self_memory:
581 self.discuss(other, self_memory)
582 return
583 # this need not be *all* the time
584 for x in other.contents:
585 if x.notable():
586 self.remember(x, other)
587 self.speak_to(other, "'I see you are carrying <indef-3>,' said <1>", [self, other, x])
588 return
589 choice = random.randint(0, 3)
590 if choice == 0:
591 self.question(other, "'Lovely weather we're having, isn't it?' asked <1>")
592 if choice == 1:
593 self.speak_to(other, "'I was wondering where you were,' said <1>")
594 elif isinstance(topic, QuestionTopic):
595 if topic.subject is not None:
596 choice = random.randint(0, 1)
597 if choice == 0:
598 self.speak_to(other, "'I know nothing about <3>, <2>,' explained <1>",
599 [self, other, topic.subject])
600 if choice == 1:
601 self.speak_to(other, "'Perhaps, <2>,' replied <1>")
602 else:
603 self.speak_to(other, "'Perhaps, <2>,' replied <1>")
604 elif isinstance(topic, WhereQuestionTopic):
605 memory = self.recall(topic.subject)
606 if not memory:
607 self.speak_to(other,
608 "'I don't know,' <1> answered simply",
609 [self, other, topic.subject])
610 elif memory.i_hid_it_there:
611 self.question(other,
612 "'Why do you want to know where <3> is, <2>?'",
613 [self, other, topic.subject])
614 elif topic.subject.location == self:
615 self.speak_to(other,
616 "'I've got <3> right here, <2>'",
617 [self, other, topic.subject])
618 self.put_down(topic.subject)
619 else:
620 if topic.subject.location.animate():
621 self.speak_to(other,
622 "'I think <3> has <4>,', <1> recalled",
623 [self, other, memory.location, topic.subject])
624 else:
625 self.speak_to(other,
626 "'I believe it's in <3>, <2>,', <1> recalled",
627 [self, other, memory.location])
628 # again, belief. hearsay. not a memory, really.
629 other.remember(topic.subject, memory.location)
630 elif isinstance(topic, SpeechTopic):
631 choice = random.randint(0, 5)
632 if choice == 0:
633 self.emit("<1> nodded", [self])
634 if choice == 1:
635 self.emit("<1> remained silent", [self])
636 if choice == 2:
637 self.question(other, "'Do you really think so?' asked <1>")
638 if choice == 3:
639 self.speak_to(other, "'Yes, it's a shame really,' stated <1>")
640 if choice == 4:
641 self.speak_to(other, "'Oh, I know, I know,' said <1>")
642 if choice == 5:
643 # -- this is getting really annoying. disable for now. --
644 # item = pick(ALL_ITEMS)
645 # self.question(other, "'But what about <3>, <2>?' posed <1>",
646 # [self, other, item], subject=item)
647 self.speak_to(other, "'I see, <2>, I see,' said <1>")
648
649 # this is its own method for indentation reasons
650 def discuss(self, other, self_memory):
651 # in general, characters should not be able to read each other's
652 # minds. however, it's convenient here. besides, their face would
653 # be pretty easy to read in this circumstance.
654 other_memory = other.recall(self_memory.subject)
655 if self_memory and not other_memory:
656 self.question(other,
657 "'Did you know there's <indef-3> in <4>?' asked <1>",
658 [self, other, self_memory.subject, self_memory.location],
659 subject=self_memory.subject)
660 return
661 if self_memory and other_memory:
662 choice = random.randint(0, 2)
663 if choice == 0:
664 self.question(other, "'Do you think we should do something about <3>?' asked <1>",
665 [self, other, self_memory.subject])
666 if choice == 1:
667 self.speak_to(other, "'I think we should do something about <3>, <2>,' said <1>",
668 [self, other, self_memory.subject])
669 if choice == 2:
670 if self.nerves == 'calm':
671 self.decide_what_to_do_about(other, self_memory.subject)
672 else:
673 if brandy.location == self:
674 self.emit("<1> poured <him-1>self a glass of brandy",
675 [self, other, self_memory.subject])
676 if brandy in self.desired_items:
677 self.desired_items.remove(brandy)
678 self.nerves = 'calm'
679 self.put_down(brandy)
680 elif self.recall(brandy):
681 self.speak_to(other,
682 "'I really must pour myself a drink,' moaned <1>",
683 [self, other, self_memory.subject],
684 subject=brandy)
685 self.desired_items.add(brandy)
686 if random.randint(0, 1) == 0:
687 self.address(other, WhereQuestionTopic(self, subject=brandy),
688 "'Where did you say <3> was?'",
689 [self, other, brandy])
690 else:
691 self.address(other, WhereQuestionTopic(self, subject=brandy),
692 "'Where is the brandy? I need a drink,' managed <1>",
693 [self, other, self_memory.subject])
694 self.desired_items.add(brandy)
695
696 # this is its own method for indentation reasons
697 def decide_what_to_do_about(self, other, thing):
698 phrase = {
699 'call': 'call the police',
700 'dispose': 'try to dispose of <3>'
701 }
702 # this should probably be affected by whether this
703 # character has, oh, i don't know, put the other at
704 # gunpoint yet, or not, or something
705 if self.what_to_do_about.get(thing) is None:
706 if random.randint(0, 1) == 0:
707 self.what_to_do_about[thing] = 'call'
708 else:
709 self.what_to_do_about[thing] = 'dispose'
710
711 if self.other_decision_about.get(thing, None) == self.what_to_do_about[thing]:
712 self.question(other,
713 ("'So we're agreed then, we should %s?' asked <1>" %
714 phrase[self.what_to_do_about[thing]]),
715 [self, other, thing])
716 # the other party might not've been aware that they agree
717 other.other_decision_about[thing] = \
718 self.what_to_do_about[thing]
719 elif self.other_decision_about.get(thing, None) is not None:
720 # WE DO NOT AGREE.
721 if revolver.location == self:
722 self.point_at(other, revolver)
723 self.address(other,
724 ThreatAgreeTopic(self, subject=thing),
725 ("'I really feel *very* strongly that we should %s, <2>,' <he-1> said between clenched teeth" %
726 phrase[self.what_to_do_about[thing]]),
727 [self, other, thing])
728 else:
729 self.speak_to(other,
730 ("'I don't think it would be a good idea to %s, <2>,' said <1>" %
731 phrase[self.other_decision_about[thing]]),
732 [self, other, thing])
733 else:
734 self.speak_to(other,
735 ("'I really think we should %s, <2>,' said <1>" %
736 phrase[self.what_to_do_about[thing]]),
737 [self, other, thing])
738 other.other_decision_about[thing] = \
739 self.what_to_do_about[thing]
740
741
742 class Male(MasculineMixin, ProperMixin, Animate):
743 pass
744
745
746 class Female(FeminineMixin, ProperMixin, Animate):
747 pass
748
749
750 ### LOCATIONS ###
751
752 class Location(Actor):
753 def __init__(self, name, enter="went to", noun="room"):
754 self.name = name
755 self.enter = enter
756 self.contents = []
757 self.exits = []
758 self.noun_ = noun
759
760 def noun(self):
761 return self.noun_
762
763 def set_exits(self, *exits):
764 for exit in exits:
765 assert isinstance(exit, Location)
766 self.exits = exits
767
768
769 class ProperLocation(ProperMixin, Location):
770 pass
771
772
773 ### OTHER INANIMATE OBJECTS ###
774
775 class Item(Actor):
776 def takeable(self):
777 return True
778
779
780 class Weapon(Item):
781 def weapon(self):
782 return True
783
784
785 class Container(Actor):
786 def container(self):
787 return True
788
789
790 class ProperContainer(ProperMixin, Container):
791 pass
792
793
794 class Treasure(Item):
795 def treasure(self):
796 return True
797
798
799 class PluralTreasure(PluralMixin, Treasure):
800 pass
801
802
803 class Horror(Actor):
804 def horror(self):
805 return True
+0
-238
src/swallows/events.py less more
0 #!/usr/bin/env python
1
2 import random
3 import sys
4
5 from swallows.util import pick
6
7 # TODO
8
9 # Diction:
10 # the event-accumulation framework could use rewriting at some point.
11 # eliminate identical duplicate sentences
12 # Bob is in the dining room & "Bob made his way to the dining room" ->
13 # "Bob wandered around for a bit, then came back to the dining room"
14 # a better solution for "Bob was in the kitchen" at the start of a paragraph;
15 # this might include significant memories Bob acquired in the last
16 # paragraph -- such as finding a revolver in the bed
17 # paragraphs should not always be the same number of events. variety!
18 # the Editor should take all the events in the chapter, and decide where
19 # paragraph breaks should go. this is difficult, because syncing up
20 # Bob's and Alice's events. timestamps?
21 # at least, the Editor should record "rich events", which include information
22 # about the main (acting) character, and where the audience last saw them
23 # use indef art when they have no memory of an item that they see
24 # dramatic irony would be really nice, but hard to pull off. Well, a certain
25 # amount happens naturally now, with character pov. but more could be done
26 # "Chapter 3. _In which Bob hides the stolen jewels in the mailbox, etc_" --
27 # i.e. chapter summaries -- that's a little too fancy to hope for, but with
28 # a sufficiently smart Editor it could be done
29
30 ### EVENTS ###
31
32 class Event(object):
33 def __init__(self, phrase, participants, excl=False):
34 """participants[0] is always the initiator, and we
35 record the location that the event was initiated in.
36
37 For now, we assume such an event can be:
38 - observed by every actor at that location
39 - affects only actors at that location
40
41 In the future, we *may* have:
42 - active and passive participants
43 - active participants all must be present at the location
44 - passive participants need not be
45
46 """
47 self.phrase = phrase
48 self.participants = participants
49 self.location = participants[0].location
50 self.excl = excl
51
52 def initiator(self):
53 return self.participants[0]
54
55 def __str__(self):
56 phrase = self.phrase
57 i = 0
58 for participant in self.participants:
59 phrase = phrase.replace('<%d>' % (i + 1), participant.render(self.participants))
60 phrase = phrase.replace('<indef-%d>' % (i + 1), participant.indefinite())
61 phrase = phrase.replace('<his-%d>' % (i + 1), participant.posessive())
62 phrase = phrase.replace('<him-%d>' % (i + 1), participant.accusative())
63 phrase = phrase.replace('<he-%d>' % (i + 1), participant.pronoun())
64 phrase = phrase.replace('<was-%d>' % (i + 1), participant.was())
65 phrase = phrase.replace('<is-%d>' % (i + 1), participant.is_())
66 i = i + 1
67 if self.excl:
68 phrase = phrase + '!'
69 else:
70 phrase = phrase + '.'
71 return phrase[0].upper() + phrase[1:]
72
73
74 class EventCollector(object):
75 def __init__(self):
76 self.events = []
77
78 def collect(self, event):
79 self.events.append(event)
80
81
82 class Oblivion(EventCollector):
83 def collect(self, event):
84 pass
85
86
87 oblivion = Oblivion()
88
89
90 # 'diction engine' -- almost exactly like a peephole optimizer -- convert
91 # "Bob went to the shed. Bob saw Alice." into
92 # "Bob went to the shed, where he saw Alice."
93 # btw, we currently get a new editor for every paragraph
94 class LegacyEditor(object):
95 """The Editor is remarkably similar to the _peephole optimizer_ in
96 compiler construction. Instead of replacing sequences of instructions
97 with more efficient but semantically equivalent sequences of
98 instructions, it replaces sequences of sentences with more readable
99 but semantically equivalent sequences of sentences.
100
101 """
102 MEMORY = 1
103
104 def __init__(self):
105 self.character = None
106 self.character_location = {}
107 self.events = []
108
109 def read(self, event):
110 if len(self.events) < self.__class__.MEMORY:
111 self.events.append(event)
112 return
113
114 character = event.participants[0]
115 # update our idea of their location
116 self.character_location[character.name] = character.location
117 # todo: check our idea of their location vs where they are,
118 # but that won't matter until an editor looks at more than one
119 # paragraph anyway
120
121 if character == self.character: # same character doing stuff
122 if event.phrase.startswith('<1>'):
123 event.phrase = '<he-1>' + event.phrase[3:]
124
125 if (self.events[-1].phrase == '<1> made <his-1> way to <2>' and
126 event.phrase == '<1> went to <2>'):
127 self.events[-1].participants[1] = event.participants[1]
128 elif (self.events[-1].phrase == '<1> went to <2>' and
129 event.phrase == '<1> went to <2>'):
130 self.events[-1].phrase = '<1> made <his-1> way to <2>'
131 self.events[-1].participants[1] = event.participants[1]
132 elif (self.events[-1].phrase == '<he-1> made <his-1> way to <2>' and
133 event.phrase == '<he-1> went to <2>'):
134 self.events[-1].participants[1] = event.participants[1]
135 elif (self.events[-1].phrase == '<he-1> went to <2>' and
136 event.phrase == '<he-1> went to <2>'):
137 self.events[-1].phrase = '<he-1> made <his-1> way to <2>'
138 self.events[-1].participants[1] = event.participants[1]
139 else:
140 self.events.append(event)
141 else: # new character doing stuff
142 self.character = character
143 self.events.append(event)
144
145
146 class LegacyPublisher(object):
147 """Publisher which uses the old Event/Editor framework.
148
149 Will probably go away eventually, but nice to have as a reference
150 while working on the new Event/Editor framework.
151
152 """
153 def __init__(self, **kwargs):
154 self.characters = kwargs.get('characters')
155 self.setting = kwargs.get('setting')
156 self.friffery = kwargs.get('friffery', False)
157 self.debug = kwargs.get('debug', False)
158 self.title = kwargs.get('title', "Untitled")
159 self.chapters = kwargs.get('chapters', 16)
160 self.paragraphs_per_chapter = kwargs.get('paragraphs_per_chapter', 25)
161
162 def publish(self):
163 print self.title
164 print "=" * len(self.title)
165 print
166
167 for chapter in range(1, self.chapters+1):
168 print "Chapter %d." % chapter
169 print "-----------"
170 print
171
172 for character in self.characters:
173 # don't continue a conversation from the previous chapter, please
174 character.topic = None
175 character.location = None
176
177 for paragraph in range(1, self.paragraphs_per_chapter+1):
178 for character in self.characters:
179 character.collector = EventCollector()
180
181 # we alternate pov like so:
182 pov_actor = (self.characters)[(paragraph - 1) % len(self.characters)]
183
184 for actor in self.characters:
185 if actor.location is None:
186 actor.place_in(pick(self.setting))
187 else:
188 # this is hacky & won't work for >2 characters:
189 if self.characters[0].location is not self.characters[1].location:
190 actor.emit("<1> was in <2>", [actor, actor.location])
191
192 while len(pov_actor.collector.events) < 20:
193 for actor in self.characters:
194 actor.live()
195
196 if self.friffery:
197 if paragraph == 1:
198 choice = random.randint(0, 3)
199 if choice == 0:
200 sys.stdout.write("It was raining. ")
201 if choice == 1:
202 sys.stdout.write("It was snowing. ")
203 if choice == 2:
204 sys.stdout.write("The sun was shining. ")
205 if choice == 3:
206 sys.stdout.write("The day was overcast and humid. ")
207 elif not str(c.events[0]).startswith("'"):
208 choice = random.randint(0, 8)
209 if choice == 0:
210 sys.stdout.write("Later on, ")
211 if choice == 1:
212 sys.stdout.write("Suddenly, ")
213 if choice == 2:
214 sys.stdout.write("After a moment's consideration, ")
215 if choice == 3:
216 sys.stdout.write("Feeling anxious, ")
217
218 if self.debug:
219 for character in self.characters:
220 print "%s'S POV:" % character.name.upper()
221 for event in character.collector.events:
222 print str(event)
223 print
224 character.dump_memory()
225 print
226 print "- - - - -"
227 print
228
229 if not self.debug:
230 editor = LegacyEditor()
231 for event in pov_actor.collector.events:
232 editor.read(event)
233 for event in editor.events:
234 sys.stdout.write(str(event) + " ")
235 #sys.stdout.write("\n")
236 print
237 print
+0
-806
src/swallows/objects.py less more
0 #!/usr/bin/env python
1
2 import random
3 import sys
4
5 from swallows.events import Event
6 from swallows.util import pick
7
8 # TODO
9
10 # they check containers while someone else is in the room? how'd that get that way?
11 # 'Hello, Alice', said Bob. 'Hello, Bob', replied Alice. NEVER GETS OLD
12 # they should always scream at seeing the dead body. the scream should
13 # be heard throughout the house and yard.
14 # ...they check that the brandy is still in the liquor cabinet. is this
15 # really necessary?
16 # certain things can't be taken, but can be dragged (like the body)
17 # path-finder between any two rooms -- not too difficult, even if it
18 # would be nicer in Prolog.
19 # "it was so nice" -- actually *have* memories of locations, and feelings
20 # (good/bad, 0 to 10 or something) about memories
21 # anxiety memory = the one they're most recently panicked about
22 # memory of whether the revolver was loaded last time they saw it
23 # calling their bluff
24 # making a run for it when at gunpoint (or trying to distract them,
25 # slap the gun away, scramble for it, etc)
26 # revolver might jam when they try to shoot it (maybe it should be a
27 # pistol instead, as those can jam more easily)
28 # dear me, someone might actually get shot. then what? another dead body?
29
30 ### TOPICS ###
31
32 # a "topic" is just what a character has recently had addressed to
33 # them. It could be anything, not just words, by another character
34 # (for example, a gesture.)
35
36 class Topic(object):
37 def __init__(self, originator, subject=None):
38 self.originator = originator
39 self.subject = subject
40
41
42 class GreetTopic(Topic):
43 pass
44
45
46 class SpeechTopic(Topic):
47 pass
48
49
50 class QuestionTopic(Topic):
51 pass
52
53
54 class WhereQuestionTopic(Topic):
55 pass
56
57
58 class ThreatGiveMeTopic(Topic):
59 pass
60
61
62 class ThreatTellMeTopic(Topic):
63 pass
64
65
66 class ThreatAgreeTopic(Topic):
67 pass
68
69
70 ### MEMORIES ###
71
72 class Memory(object):
73 def __init__(self, subject, location, i_hid_it_there=False):
74 self.subject = subject # the thing being remembered
75 self.location = location # where we last remember seeing it
76 self.i_hid_it_there = i_hid_it_there
77
78
79 ### ACTORS (objects in the world) ###
80
81 class Actor(object):
82 def __init__(self, name, location=None, collector=None):
83 self.name = name
84 self.collector = collector
85 self.contents = []
86 self.enter = ""
87 self.location = None
88 if location is not None:
89 self.move_to(location)
90
91 def notable(self):
92 return self.treasure() or self.weapon() or self.animate() or self.horror()
93
94 def treasure(self):
95 return False
96
97 def weapon(self):
98 return False
99
100 def horror(self):
101 return False
102
103 def takeable(self):
104 return False
105
106 def animate(self):
107 return False
108
109 def container(self):
110 return False
111
112 def article(self):
113 return 'the'
114
115 def posessive(self):
116 return "its"
117
118 def accusative(self):
119 return "it"
120
121 def pronoun(self):
122 return "it"
123
124 def was(self):
125 return "was"
126
127 def is_(self):
128 return "is"
129
130 def emit(self, *args, **kwargs):
131 if self.collector:
132 self.collector.collect(Event(*args, **kwargs))
133
134 def move_to(self, location):
135 if self.location:
136 self.location.contents.remove(self)
137 self.location = location
138 self.location.contents.append(self)
139
140 def render(self, participants):
141 name = self.name
142 if participants:
143 subject = participants[0]
144 posessive = subject.name + "'s"
145 name = name.replace(posessive, subject.posessive())
146 article = self.article()
147 if not article:
148 return name
149 return '%s %s' % (article, name)
150
151 def indefinite(self):
152 article = 'a'
153 if self.name.startswith(('a', 'e', 'i', 'o', 'u')):
154 article = 'an'
155 return '%s %s' % (article, self.name)
156
157 # for debugging
158 def dump_memory(self):
159 for thing in self.memories:
160 memory = self.memories[thing]
161 print ".oO{ %s is in %s }" % (
162 memory.subject.render([]),
163 memory.location.render([]))
164 if memory.i_hid_it_there:
165 print ".oO{ I hid it there }"
166 print "desired items:", repr(self.desired_items)
167 print "decisions:", repr(self.what_to_do_about)
168 print "knowledge of others' decisions:", repr(self.other_decision_about)
169
170
171 ### unfortunate externals ###
172
173 # items that the mechanics need to know about; they are defined "for reals"
174 # in swallows.world, which may not even be used, so for now we make stand-in
175 # dummy objects for them
176 revolver = Actor('non-existent revolver')
177 brandy = Actor('non-existent brandy')
178 dead_body = Actor('non-existent dead body')
179
180
181 ### some mixins for Actors ###
182
183 class ProperMixin(object):
184 def article(self):
185 return ''
186
187
188 class PluralMixin(object):
189 def posessive(self):
190 return "their"
191
192 def accusative(self):
193 return "them"
194
195 def pronoun(self):
196 return "they"
197
198 def indefinite(self):
199 article = 'some'
200 return '%s %s' % (article, self.name)
201
202 def was(self):
203 return "were"
204
205 def is_(self):
206 return "are"
207
208
209 class MasculineMixin(object):
210 def posessive(self):
211 return "his"
212
213 def accusative(self):
214 return "him"
215
216 def pronoun(self):
217 return "he"
218
219
220 class FeminineMixin(object):
221 def posessive(self):
222 return "her"
223
224 def accusative(self):
225 return "her"
226
227 def pronoun(self):
228 return "she"
229
230
231 ### ANIMATE OBJECTS ###
232
233 class Animate(Actor):
234 def __init__(self, name, location=None, collector=None):
235 Actor.__init__(self, name, location=location, collector=None)
236 self.topic = None
237 # hash of actor object to Memory object
238 self.memories = {}
239 self.desired_items = set()
240 # this should really be *derived* from having a recent memory
241 # of seeing a dead body in the bathroom. but for now,
242 self.nerves = 'calm'
243 # this, too, should be more sophisticated.
244 # it is neither a memory, nor a belief, but a judgment, and
245 # eventually possibly a goal.
246 # hash maps Actors to strings
247 self.what_to_do_about = {}
248 self.other_decision_about = {}
249
250 def animate(self):
251 return True
252
253 def remember(self, thing, location, i_hid_it_there=False):
254 assert isinstance(thing, Actor)
255 self.memories[thing] = Memory(thing, location, i_hid_it_there=i_hid_it_there)
256
257 def recall(self, thing):
258 assert isinstance(thing, Actor)
259 return self.memories.get(thing, None)
260
261 def address(self, other, topic, phrase, participants=None):
262 if participants is None:
263 participants = [self, other]
264 other.topic = topic
265 # in the absence of a better event-collection system
266 # we do this sort of thing when >1 actor can observe an event:
267 self.emit(phrase, participants)
268 other.emit(phrase, participants)
269
270 def greet(self, other, phrase, participants=None):
271 self.address(other, GreetTopic(self), phrase, participants)
272
273 def speak_to(self, other, phrase, participants=None, subject=None):
274 self.address(other, SpeechTopic(self, subject=subject), phrase, participants)
275
276 def question(self, other, phrase, participants=None, subject=None):
277 self.address(other, QuestionTopic(self, subject=subject), phrase, participants)
278
279 def place_in(self, location):
280 # like move_to but quieter; for setting up scenes etc
281 if self.location is not None:
282 self.location.contents.remove(self)
283 self.location = location
284 self.location.contents.append(self)
285 self.emit("<1> <was-1> in <2>", [self, self.location])
286 for x in self.location.contents:
287 if x == self:
288 continue
289 if x.notable():
290 self.emit("<1> saw <2>", [self, x])
291 self.remember(x, self.location)
292
293 def move_to(self, location):
294 assert(location != self.location)
295 assert(location is not None)
296 for x in self.location.contents:
297 # otherwise we get "Bob saw Bob leave the room", eh?
298 if x is self:
299 continue
300 if x.animate():
301 x.emit("<1> saw <2> leave the %s" % x.location.noun(), [x, self])
302 if self.location is not None:
303 self.location.contents.remove(self)
304 self.location = location
305 self.location.contents.append(self)
306 self.emit("<1> went to <2>", [self, self.location])
307 if random.randint(0, 10) == 0:
308 self.emit("It was so nice being in <2> again",
309 [self, self.location], excl=True)
310
311 # okay, look around you.
312 for x in self.location.contents:
313 if x == self:
314 continue
315 if x.horror():
316 memory = self.recall(x)
317 if memory:
318 amount = pick(['shudder', 'wave'])
319 emotion = pick(['fear', 'disgust', 'sickness', 'loathing'])
320 self.emit("<1> felt a %s of %s as <he-1> looked at <2>" % (amount, emotion), [self, x])
321 self.remember(x, self.location)
322 else:
323 verb = pick(['screamed', 'yelped', 'went pale'])
324 self.emit("<1> %s at the sight of <indef-2>" % verb, [self, x], excl=True)
325 self.remember(x, self.location)
326 self.nerves = 'shaken'
327 elif x.animate():
328 other = x
329 self.emit("<1> saw <2>", [self, other])
330 other.emit("<1> saw <2> walk into the %s" % self.location.noun(), [other, self])
331 self.remember(x, self.location)
332 self.greet(x, "'Hello, <2>,' said <1>")
333 for y in other.contents:
334 if y.treasure():
335 self.emit(
336 "<1> noticed <2> <was-2> carrying <indef-3>",
337 [self, other, y])
338 if revolver.location == self:
339 self.point_at(other, revolver)
340 self.address(other,
341 ThreatGiveMeTopic(self, subject=y),
342 "'Please give me <3>, <2>, or I shall shoot you,' <he-1> said",
343 [self, other, y])
344 return
345 # another case of mind-reading. well, it helps the story advance!
346 # (it would help more to double-check this against your OWN memory)
347 if revolver.location == self:
348 for thing in other.memories:
349 memory = other.recall(thing)
350 self_memory = self.recall(thing)
351 if self_memory:
352 continue
353 if memory.i_hid_it_there and memory.subject is not revolver:
354 self.point_at(other, revolver)
355 self.address(other,
356 ThreatTellMeTopic(self, subject=thing),
357 "'Tell me where you have hidden <3>, <2>, or I shall shoot you,' <he-1> said",
358 [self, other, thing])
359 return
360 elif x.notable():
361 self.emit("<1> saw <2>", [self, x])
362 self.remember(x, self.location)
363
364 def point_at(self, other, item):
365 # it would be nice if there was some way to
366 # indicate the revolver as part of the Topic which will follow,
367 # or otherwise indicate the context as "at gunpoint"
368
369 # XXX SERIOUSLY WE HAVE TO FIX THIS
370 # assert self.location == other.location
371 assert item.location == self
372 self.emit("<1> pointed <3> at <2>",
373 [self, other, item])
374 other.emit("<1> pointed <3> at <2>",
375 [self, other, item])
376 other.remember(item, self)
377
378 def put_down(self, item):
379 assert(item.location == self)
380 self.emit("<1> put down <2>", [self, item])
381 item.move_to(self.location)
382 self.remember(item, self.location)
383 for other in self.location.contents:
384 if other is self:
385 continue
386 if other.animate():
387 other.emit("<1> put down <2>", [self, item])
388 other.remember(item, self.location)
389
390 def pick_up(self, item):
391 assert(item.location == self.location)
392 self.emit("<1> picked up <2>", [self, item])
393 item.move_to(self)
394 self.remember(item, self)
395 for other in self.location.contents:
396 if other is self:
397 continue
398 if other.animate():
399 other.emit("<1> picked up <2>", [self, item])
400 other.remember(item, self)
401
402 def give_to(self, other, item):
403 assert(item.location == self)
404 # XXX seriously? this isn't preserved? blast
405 # assert(self.location == other.location)
406 self.emit("<1> gave <3> to <2>", [self, other, item])
407 other.emit("<1> gave <3> to <2>", [self, other, item])
408 item.move_to(other)
409 self.remember(item, other)
410 other.remember(item, other)
411
412 def wander(self):
413 self.move_to(
414 self.location.exits[
415 random.randint(0, len(self.location.exits)-1)
416 ]
417 )
418
419 def live(self):
420 # first, if in a conversation, turn total attention to that
421 if self.topic is not None:
422 return self.converse(self.topic)
423
424 # otherwise, if there are items here that you desire, you *must* pick
425 # them up.
426 for x in self.location.contents:
427 if x.treasure() or x.weapon() or x in self.desired_items:
428 self.pick_up(x)
429 return
430 people_about = False
431
432 # otherwise, fixate on some valuable object (possibly the revolver)
433 # that you are carrying:
434 fixated_on = None
435 for y in self.contents:
436 if y.treasure():
437 fixated_on = y
438 break
439 if not fixated_on and random.randint(0, 20) == 0 and revolver.location == self:
440 fixated_on = revolver
441
442 # check if you are alone
443 for x in self.location.contents:
444 if x.animate() and x is not self:
445 people_about = True
446
447 choice = random.randint(0, 25)
448 if choice < 10 and not people_about:
449 return self.hide_and_seek(fixated_on)
450 if choice < 20:
451 return self.wander()
452 if choice == 20:
453 self.emit("<1> yawned", [self])
454 elif choice == 21:
455 self.emit("<1> gazed thoughtfully into the distance", [self])
456 elif choice == 22:
457 self.emit("<1> thought <he-1> heard something", [self])
458 elif choice == 23:
459 self.emit("<1> scratched <his-1> head", [self])
460 elif choice == 24:
461 self.emit("<1> immediately had a feeling something was amiss", [self])
462 else:
463 return self.wander()
464
465 def hide_and_seek(self, fixated_on):
466 # check for some place to hide the thing you're fixating on
467 containers = []
468 for x in self.location.contents:
469 if x.container():
470 # did I hide something here previously?
471 memories = []
472 for thing in self.memories:
473 memory = self.recall(thing)
474 if memory.location == x:
475 memories.append(memory)
476 containers.append((x, memories))
477 if not containers:
478 return self.wander()
479 # ok! we now have a list of containers, each of which has zero or
480 # more memories of things being in it.
481 if fixated_on:
482 (container, memories) = pick(containers)
483 self.emit("<1> hid <2> in <3>", [self, fixated_on, container])
484 fixated_on.move_to(container)
485 self.remember(fixated_on, container, i_hid_it_there=True)
486 return self.wander()
487 else:
488 # we're looking for treasure!
489 # todo: it would maybe be better to prioritize this selection
490 (container, memories) = pick(containers)
491 # sometimes, we don't care what we think we know about something
492 # (this lets us, for example, explore things in hopes of brandy)
493 if memories and random.randint(0, 3) == 0:
494 memories = None
495 if memories:
496 memory = pick(memories)
497 picking_up = random.randint(0, 5) == 0
498 if memory.subject is revolver:
499 picking_up = True
500 if picking_up:
501 if memory.i_hid_it_there:
502 self.emit("<1> retrieved <3> <he-1> had hidden in <2>",
503 [self, container, memory.subject])
504 else:
505 self.emit("<1> retrieved <3> from <2>",
506 [self, container, memory.subject])
507 # but!
508 if memory.subject.location != container:
509 self.emit("But <he-2> <was-2> missing", [self, memory.subject], excl=True)
510 # forget ALLLLLLL about it, then. so realistic!
511 del self.memories[memory.subject]
512 else:
513 memory.subject.move_to(self)
514 self.remember(memory.subject, self)
515 else:
516 self.emit("<1> checked that <3> <was-3> still in <2>",
517 [self, container, memory.subject])
518 # but!
519 if memory.subject.location != container:
520 self.emit("But <he-2> <was-2> missing", [self, memory.subject], excl=True)
521 del self.memories[memory.subject]
522 else: # no memories of this
523 self.emit("<1> searched <2>", [self, container])
524 desired_things = []
525 for thing in container.contents:
526 # remember what you saw whilst searching this container
527 self.remember(thing, container)
528 if thing.treasure() or thing.weapon() or thing in self.desired_items:
529 desired_things.append(thing)
530 if desired_things:
531 thing = pick(desired_things)
532 self.emit("<1> found <2> there, and took <him-2>", [self, thing])
533 thing.move_to(self)
534 self.remember(thing, self)
535
536 def converse(self, topic):
537 self.topic = None
538 other = topic.originator
539 if isinstance(topic, ThreatGiveMeTopic):
540 found_object = None
541 for x in self.contents:
542 if x is topic.subject:
543 found_object = x
544 break
545 if not found_object:
546 self.speak_to(other,
547 "'But I don't have <3>!' protested <1>",
548 [self, other, topic.subject])
549 else:
550 self.speak_to(other,
551 "'Please don't shoot!', <1> cried",
552 [self, other, found_object])
553 self.give_to(other, found_object)
554 elif isinstance(topic, ThreatTellMeTopic):
555 memory = self.recall(topic.subject)
556 if not memory:
557 self.speak_to(other,
558 "'I have no memory of that, <2>,' <1> replied",
559 [self, other, topic.subject])
560 else:
561 self.speak_to(other,
562 "'Please don't shoot!', <1> cried, '<he-3> <is-3> in <4>'",
563 [self, other, topic.subject, memory.location])
564 # this is not really a *memory*, btw, it's a *belief*
565 other.remember(topic.subject, memory.location)
566 elif isinstance(topic, ThreatAgreeTopic):
567 decision = self.what_to_do_about[topic.subject]
568 self.speak_to(other,
569 "'You make a persuasive case for remaining undecided, <2>,' said <1>",
570 [self, other])
571 del self.what_to_do_about[topic.subject]
572 del other.other_decision_about[topic.subject]
573 elif isinstance(topic, GreetTopic):
574 # emit, because making this a speak_to leads to too much silliness
575 self.emit("'Hello, <2>,' replied <1>", [self, other])
576 # but otoh this sort of thing does not scale:
577 other.emit("'Hello, <2>,' replied <1>", [self, other])
578 # this needs to be more general
579 self_memory = self.recall(dead_body)
580 if self_memory:
581 self.discuss(other, self_memory)
582 return
583 # this need not be *all* the time
584 for x in other.contents:
585 if x.notable():
586 self.remember(x, other)
587 self.speak_to(other, "'I see you are carrying <indef-3>,' said <1>", [self, other, x])
588 return
589 choice = random.randint(0, 3)
590 if choice == 0:
591 self.question(other, "'Lovely weather we're having, isn't it?' asked <1>")
592 if choice == 1:
593 self.speak_to(other, "'I was wondering where you were,' said <1>")
594 elif isinstance(topic, QuestionTopic):
595 if topic.subject is not None:
596 choice = random.randint(0, 1)
597 if choice == 0:
598 self.speak_to(other, "'I know nothing about <3>, <2>,' explained <1>",
599 [self, other, topic.subject])
600 if choice == 1:
601 self.speak_to(other, "'Perhaps, <2>,' replied <1>")
602 else:
603 self.speak_to(other, "'Perhaps, <2>,' replied <1>")
604 elif isinstance(topic, WhereQuestionTopic):
605 memory = self.recall(topic.subject)
606 if not memory:
607 self.speak_to(other,
608 "'I don't know,' <1> answered simply",
609 [self, other, topic.subject])
610 elif memory.i_hid_it_there:
611 self.question(other,
612 "'Why do you want to know where <3> is, <2>?'",
613 [self, other, topic.subject])
614 elif topic.subject.location == self:
615 self.speak_to(other,
616 "'I've got <3> right here, <2>'",
617 [self, other, topic.subject])
618 self.put_down(topic.subject)
619 else:
620 if topic.subject.location.animate():
621 self.speak_to(other,
622 "'I think <3> has <4>,', <1> recalled",
623 [self, other, memory.location, topic.subject])
624 else:
625 self.speak_to(other,
626 "'I believe it's in <3>, <2>,', <1> recalled",
627 [self, other, memory.location])
628 # again, belief. hearsay. not a memory, really.
629 other.remember(topic.subject, memory.location)
630 elif isinstance(topic, SpeechTopic):
631 choice = random.randint(0, 5)
632 if choice == 0:
633 self.emit("<1> nodded", [self])
634 if choice == 1:
635 self.emit("<1> remained silent", [self])
636 if choice == 2:
637 self.question(other, "'Do you really think so?' asked <1>")
638 if choice == 3:
639 self.speak_to(other, "'Yes, it's a shame really,' stated <1>")
640 if choice == 4:
641 self.speak_to(other, "'Oh, I know, I know,' said <1>")
642 if choice == 5:
643 # -- this is getting really annoying. disable for now. --
644 # item = pick(ALL_ITEMS)
645 # self.question(other, "'But what about <3>, <2>?' posed <1>",
646 # [self, other, item], subject=item)
647 self.speak_to(other, "'I see, <2>, I see,' said <1>")
648
649 # this is its own method for indentation reasons
650 def discuss(self, other, self_memory):
651 # in general, characters should not be able to read each other's
652 # minds. however, it's convenient here. besides, their face would
653 # be pretty easy to read in this circumstance.
654 other_memory = other.recall(self_memory.subject)
655 if self_memory and not other_memory:
656 self.question(other,
657 "'Did you know there's <indef-3> in <4>?' asked <1>",
658 [self, other, self_memory.subject, self_memory.location],
659 subject=self_memory.subject)
660 return
661 if self_memory and other_memory:
662 choice = random.randint(0, 2)
663 if choice == 0:
664 self.question(other, "'Do you think we should do something about <3>?' asked <1>",
665 [self, other, self_memory.subject])
666 if choice == 1:
667 self.speak_to(other, "'I think we should do something about <3>, <2>,' said <1>",
668 [self, other, self_memory.subject])
669 if choice == 2:
670 if self.nerves == 'calm':
671 self.decide_what_to_do_about(other, self_memory.subject)
672 else:
673 if brandy.location == self:
674 self.emit("<1> poured <him-1>self a glass of brandy",
675 [self, other, self_memory.subject])
676 if brandy in self.desired_items:
677 self.desired_items.remove(brandy)
678 self.nerves = 'calm'
679 self.put_down(brandy)
680 elif self.recall(brandy):
681 self.speak_to(other,
682 "'I really must pour myself a drink,' moaned <1>",
683 [self, other, self_memory.subject],
684 subject=brandy)
685 self.desired_items.add(brandy)
686 if random.randint(0, 1) == 0:
687 self.address(other, WhereQuestionTopic(self, subject=brandy),
688 "'Where did you say <3> was?'",
689 [self, other, brandy])
690 else:
691 self.address(other, WhereQuestionTopic(self, subject=brandy),
692 "'Where is the brandy? I need a drink,' managed <1>",
693 [self, other, self_memory.subject])
694 self.desired_items.add(brandy)
695
696 # this is its own method for indentation reasons
697 def decide_what_to_do_about(self, other, thing):
698 phrase = {
699 'call': 'call the police',
700 'dispose': 'try to dispose of <3>'
701 }
702 # this should probably be affected by whether this
703 # character has, oh, i don't know, put the other at
704 # gunpoint yet, or not, or something
705 if self.what_to_do_about.get(thing) is None:
706 if random.randint(0, 1) == 0:
707 self.what_to_do_about[thing] = 'call'
708 else:
709 self.what_to_do_about[thing] = 'dispose'
710
711 if self.other_decision_about.get(thing, None) == self.what_to_do_about[thing]:
712 self.question(other,
713 ("'So we're agreed then, we should %s?' asked <1>" %
714 phrase[self.what_to_do_about[thing]]),
715 [self, other, thing])
716 # the other party might not've been aware that they agree
717 other.other_decision_about[thing] = \
718 self.what_to_do_about[thing]
719 elif self.other_decision_about.get(thing, None) is not None:
720 # WE DO NOT AGREE.
721 if revolver.location == self:
722 self.point_at(other, revolver)
723 self.address(other,
724 ThreatAgreeTopic(self, subject=thing),
725 ("'I really feel *very* strongly that we should %s, <2>,' <he-1> said between clenched teeth" %
726 phrase[self.what_to_do_about[thing]]),
727 [self, other, thing])
728 else:
729 self.speak_to(other,
730 ("'I don't think it would be a good idea to %s, <2>,' said <1>" %
731 phrase[self.other_decision_about[thing]]),
732 [self, other, thing])
733 else:
734 self.speak_to(other,
735 ("'I really think we should %s, <2>,' said <1>" %
736 phrase[self.what_to_do_about[thing]]),
737 [self, other, thing])
738 other.other_decision_about[thing] = \
739 self.what_to_do_about[thing]
740
741
742 class Male(MasculineMixin, ProperMixin, Animate):
743 pass
744
745
746 class Female(FeminineMixin, ProperMixin, Animate):
747 pass
748
749
750 ### LOCATIONS ###
751
752 class Location(Actor):
753 def __init__(self, name, enter="went to", noun="room"):
754 self.name = name
755 self.enter = enter
756 self.contents = []
757 self.exits = []
758 self.noun_ = noun
759
760 def noun(self):
761 return self.noun_
762
763 def set_exits(self, *exits):
764 for exit in exits:
765 assert isinstance(exit, Location)
766 self.exits = exits
767
768
769 class ProperLocation(ProperMixin, Location):
770 pass
771
772
773 ### OTHER INANIMATE OBJECTS ###
774
775 class Item(Actor):
776 def takeable(self):
777 return True
778
779
780 class Weapon(Item):
781 def weapon(self):
782 return True
783
784
785 class Container(Actor):
786 def container(self):
787 return True
788
789
790 class ProperContainer(ProperMixin, Container):
791 pass
792
793
794 class Treasure(Item):
795 def treasure(self):
796 return True
797
798
799 class PluralTreasure(PluralMixin, Treasure):
800 pass
801
802
803 class Horror(Actor):
804 def horror(self):
805 return True
0 #!/usr/bin/env python
1
2 from swallows.engine.objects import (
3 Location, ProperLocation, Treasure, PluralTreasure,
4 Container, ProperContainer,
5 Item, Weapon, Horror, Male, Female
6 )
7 from swallows.util import pick
8
9 # TODO
10
11 # World:
12 # more reacting to the dead body:
13 # - if they *agree*, take one of the courses of action
14 # - if they *disagree*, well... the revolver may prove persuasive
15 # after agreement:
16 # - calling the police (do they have a landline? it might be entertaining
17 # if they share one mobile phone between the both of them)
18 # - i'll have to introduce a new character... the detective. yow.
19 # - trying to dispose of it... they try to drag it to... the garden?
20 # i'll have to add a garden. and a shovel.
21 # an unspeakable thing in the basement! (don't they have enough excitement
22 # in their lives?)
23 # bullets for the revolver
24
25 ### world ###
26
27 kitchen = Location('kitchen')
28 living_room = Location('living room')
29 dining_room = Location('dining room')
30 front_hall = Location('front hall')
31 driveway = Location('driveway', noun="driveway")
32 garage = Location('garage', noun="garage")
33 path_by_the_shed = Location('path by the shed', noun="path")
34 shed = Location('shed', noun="shed")
35 upstairs_hall = Location('upstairs hall')
36 study = Location('study')
37 bathroom = Location('bathroom')
38 bobs_bedroom = ProperLocation("Bob's bedroom")
39 alices_bedroom = ProperLocation("Alice's bedroom")
40
41 kitchen.set_exits(dining_room, front_hall)
42 living_room.set_exits(dining_room, front_hall)
43 dining_room.set_exits(living_room, kitchen)
44 front_hall.set_exits(kitchen, living_room, driveway, upstairs_hall)
45 driveway.set_exits(front_hall, garage, path_by_the_shed)
46 garage.set_exits(driveway)
47 path_by_the_shed.set_exits(driveway, shed)
48 shed.set_exits(path_by_the_shed)
49 upstairs_hall.set_exits(bobs_bedroom, alices_bedroom, front_hall, study, bathroom)
50 bobs_bedroom.set_exits(upstairs_hall)
51 alices_bedroom.set_exits(upstairs_hall)
52 study.set_exits(upstairs_hall)
53 bathroom.set_exits(upstairs_hall)
54
55 house = (kitchen, living_room, dining_room, front_hall, driveway, garage,
56 upstairs_hall, bobs_bedroom, alices_bedroom, study, bathroom,
57 path_by_the_shed, shed)
58
59 falcon = Treasure('golden falcon', location=dining_room)
60 jewels = PluralTreasure('stolen jewels', location=garage)
61
62 cupboards = Container('cupboards', location=kitchen)
63 liquor_cabinet = Container('liquor cabinet', location=dining_room)
64 mailbox = Container('mailbox', location=driveway)
65
66 bobs_bed = ProperContainer("Bob's bed", location=bobs_bedroom)
67 alices_bed = ProperContainer("Alice's bed", location=alices_bedroom)
68
69 brandy = Item('bottle of brandy', location=liquor_cabinet)
70 revolver = Weapon('revolver', location=pick([bobs_bed, alices_bed]))
71 dead_body = Horror('dead body', location=bathroom)
72
73 alice = Female('Alice')
74 bob = Male('Bob')
75
76 ALL_ITEMS = (falcon, jewels, revolver, brandy)
+0
-77
src/swallows/world.py less more
0 #!/usr/bin/env python
1
2 from swallows.objects import (
3 Location, ProperLocation, Treasure, PluralTreasure,
4 Container, ProperContainer,
5 Item, Weapon, Horror, Male, Female
6 )
7 from swallows.util import pick
8
9 # TODO
10
11 # World:
12 # more reacting to the dead body:
13 # - if they *agree*, take one of the courses of action
14 # - if they *disagree*, well... the revolver may prove persuasive
15 # after agreement:
16 # - calling the police (do they have a landline? it might be entertaining
17 # if they share one mobile phone between the both of them)
18 # - i'll have to introduce a new character... the detective. yow.
19 # - trying to dispose of it... they try to drag it to... the garden?
20 # i'll have to add a garden. and a shovel.
21 # an unspeakable thing in the basement! (don't they have enough excitement
22 # in their lives?)
23 # bullets for the revolver
24
25 ### world ###
26
27 kitchen = Location('kitchen')
28 living_room = Location('living room')
29 dining_room = Location('dining room')
30 front_hall = Location('front hall')
31 driveway = Location('driveway', noun="driveway")
32 garage = Location('garage', noun="garage")
33 path_by_the_shed = Location('path by the shed', noun="path")
34 shed = Location('shed', noun="shed")
35 upstairs_hall = Location('upstairs hall')
36 study = Location('study')
37 bathroom = Location('bathroom')
38 bobs_bedroom = ProperLocation("Bob's bedroom")
39 alices_bedroom = ProperLocation("Alice's bedroom")
40
41 kitchen.set_exits(dining_room, front_hall)
42 living_room.set_exits(dining_room, front_hall)
43 dining_room.set_exits(living_room, kitchen)
44 front_hall.set_exits(kitchen, living_room, driveway, upstairs_hall)
45 driveway.set_exits(front_hall, garage, path_by_the_shed)
46 garage.set_exits(driveway)
47 path_by_the_shed.set_exits(driveway, shed)
48 shed.set_exits(path_by_the_shed)
49 upstairs_hall.set_exits(bobs_bedroom, alices_bedroom, front_hall, study, bathroom)
50 bobs_bedroom.set_exits(upstairs_hall)
51 alices_bedroom.set_exits(upstairs_hall)
52 study.set_exits(upstairs_hall)
53 bathroom.set_exits(upstairs_hall)
54
55 house = (kitchen, living_room, dining_room, front_hall, driveway, garage,
56 upstairs_hall, bobs_bedroom, alices_bedroom, study, bathroom,
57 path_by_the_shed, shed)
58
59 falcon = Treasure('golden falcon', location=dining_room)
60 jewels = PluralTreasure('stolen jewels', location=garage)
61
62 cupboards = Container('cupboards', location=kitchen)
63 liquor_cabinet = Container('liquor cabinet', location=dining_room)
64 mailbox = Container('mailbox', location=driveway)
65
66 bobs_bed = ProperContainer("Bob's bed", location=bobs_bedroom)
67 alices_bed = ProperContainer("Alice's bed", location=alices_bedroom)
68
69 brandy = Item('bottle of brandy', location=liquor_cabinet)
70 revolver = Weapon('revolver', location=pick([bobs_bed, alices_bed]))
71 dead_body = Horror('dead body', location=bathroom)
72
73 alice = Female('Alice')
74 bob = Male('Bob')
75
76 ALL_ITEMS = (falcon, jewels, revolver, brandy)