Split engine and story into two sub-packages.
Cat's Eye Technologies
11 years ago
11 | 11 | sys.path.insert(0, join(dirname(realpath(sys.argv[0])), '..', 'src')) |
12 | 12 | |
13 | 13 | # 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 | |
16 | 16 | |
17 | 17 | ### world ### |
18 | 18 |
10 | 10 | sys.path.insert(0, join(dirname(realpath(sys.argv[0])), '..', 'src')) |
11 | 11 | |
12 | 12 | # 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 | |
15 | 15 | |
16 | 16 | # the following is not good. but it works. |
17 | 17 | # better options would be: |
18 | 18 | # - define the world-specific behaviour of the characters in swallows.world |
19 | 19 | # - (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 | |
25 | 25 | |
26 | 26 | # we extend it by adding a new character |
27 | 27 | fred = Male('Fred') |
30 | 30 | |
31 | 31 | publisher = LegacyPublisher( |
32 | 32 | characters=( |
33 | swallows.world.alice, | |
34 | swallows.world.bob, | |
33 | swallows.story.world.alice, | |
34 | swallows.story.world.bob, | |
35 | 35 | fred |
36 | 36 | ), |
37 | setting=swallows.world.house, | |
37 | setting=swallows.story.world.house, | |
38 | 38 | title="My _The Swallows_ Fanfic", |
39 | 39 | #debug=True, |
40 | 40 | ) |
10 | 10 | # get the ../src/ directory onto the Python module search path |
11 | 11 | sys.path.insert(0, join(dirname(realpath(sys.argv[0])), '..', 'src')) |
12 | 12 | |
13 | # now we can: | |
14 | from swallows.events import LegacyPublisher | |
13 | # now we can import things, like: | |
14 | from swallows.engine.events import LegacyPublisher | |
15 | 15 | |
16 | 16 | # the following is not good. but it works. |
17 | 17 | # better options would be: |
18 | 18 | # - define the world-specific behaviour of the characters in swallows.world |
19 | 19 | # - (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 | |
25 | 25 | |
26 | 26 | ### main ### |
27 | 27 | |
28 | 28 | publisher = LegacyPublisher( |
29 | 29 | characters=( |
30 | swallows.world.alice, | |
31 | swallows.world.bob, | |
30 | swallows.story.world.alice, | |
31 | swallows.story.world.bob, | |
32 | 32 | ), |
33 | setting=swallows.world.house, | |
33 | setting=swallows.story.world.house, | |
34 | 34 | title="Title TBD (Book Four of _The Swallows_ series)", |
35 | 35 | #debug=True, |
36 | 36 | ) |
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 | ||
166 | ||
167 | for chapter in range(1, self.chapters+1): | |
168 | print "Chapter %d." % chapter | |
169 | print "-----------" | |
170 | ||
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 | ||
224 | character.dump_memory() | |
225 | ||
226 | print "- - - - -" | |
227 | ||
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 | ||
237 |
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 | #!/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 | ||
166 | ||
167 | for chapter in range(1, self.chapters+1): | |
168 | print "Chapter %d." % chapter | |
169 | print "-----------" | |
170 | ||
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 | ||
224 | character.dump_memory() | |
225 | ||
226 | print "- - - - -" | |
227 | ||
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 | ||
237 |
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 | #!/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) |