git @ Cat's Eye Technologies MARYSUE / 1b50ef2
Initial import of MARYSUE sources. Chris Pressey 6 years ago
21 changed file(s) with 4836 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 MARYSUE
1 =======
2
3 Original Generator! Do Not Steal!!1!
4
5 Built for NaNoGenMo 2015. Self-contained; requires only Python 2.7.
6
7 Usage:
8
9 bin/MARYSUE
10
11 will generate a novel and dump it in the Markdown format. To create an
12 HTML file (requires pandoc) and display it in Firefox,
13
14 bin/MARYSUE --publish
15
16 More details TBW.
0 #!/usr/bin/env python
1
2 """Usage: MARYSUE {options}
3
4 MARYSUE - Original Generator! Do Not Steal!!1!
5
6 """
7
8 # --------
9 from os.path import realpath, dirname, join
10 import sys
11 sys.path.insert(0, join(dirname(realpath(sys.argv[0])), '..', 'src'))
12 sys.path.insert(0, join(dirname(realpath(sys.argv[0])), '..'))
13 # --------
14
15 from optparse import OptionParser
16
17 import marysue.util as random
18 from marysue.plot import *
19 from marysue.plotter import Plotter
20 from marysue.publisher import Novel
21
22 from stories import serenity
23
24
25 ### default chapter configuration ###
26
27 CHAPTER_COUNT = 40
28
29
30 #print random.randint(0, 100)
31
32 chapters = [
33 {
34 'position': 'beginning',
35 'plot_min': [
36 (LoseItem, 1),
37 ],
38 'plot_max': [
39 (LoseItem, 1),
40 (Kidnapping, 0),
41 ],
42 },
43 ] + [
44 {
45 'position': 'beginning',
46 } for _ in xrange(0, 9)
47 ] + [
48 {
49 'position': 'middle',
50 } for _ in xrange(0, CHAPTER_COUNT - (10 + 10))
51 ] + [
52 {
53 'position': 'end',
54 } for _ in xrange(0, 9)
55 ] + [
56 {
57 'position': 'final',
58 'plot_min': [
59 (RomanticResolution, 1),
60 ],
61 'plot_max': [
62 (RomanticResolution, 1),
63 (AwkwardTension, 0),
64 (RomanticTension, 0),
65 (AwkwardCombat, 0),
66 ],
67 }
68 ]
69
70 for n, chapter in enumerate(chapters):
71 chapter.setdefault('plot_depth', 5)
72 chapter.setdefault('plot_constraints', [])
73 chapter.setdefault('plot_max', [])
74 chapter.setdefault('plot_min', [])
75 if n < (CHAPTER_COUNT / 4):
76 chapter['plot_max'].append((AwkwardTension, 1))
77 if n < (CHAPTER_COUNT / 2):
78 chapter['plot_max'].append((RomanticTension, 0))
79 if chapter['position'] != 'final':
80 chapter['plot_max'].append((RomanticResolution, 0))
81
82
83 ### MAIN ###
84
85 optparser = OptionParser(__doc__.strip())
86 optparser.add_option('--debug', action="store_true", default=False,
87 help="trace some things inside the compiler")
88 optparser.add_option('--dump', action="store_true", default=False,
89 help="show story tree in schematic format")
90 optparser.add_option('--plot-depth', default='5',
91 help="depth of plot to generate")
92 optparser.add_option('--plot-max', default=None,
93 help="comma-seperated list of classname:count "
94 "and the generated story will contain at most "
95 "count occurrences of each plot class")
96 optparser.add_option('--plot-min', default=None,
97 help="comma-seperated list of classname:count "
98 "and the generated story will contain at least "
99 "count occurrences of each plot class")
100 optparser.add_option('--synopsis', action="store_true", default=False,
101 help="just dump a synopsis of the plot")
102 optparser.add_option('--disable-shuffle-demon', action="store_true", default=False,
103 help="disable the Shuffle Demon")
104 optparser.add_option('--publish', action="store_true", default=False,
105 help="generate an HTML5 file and open in browser "
106 "(requires pandoc and firefox)")
107
108 (options, args) = optparser.parse_args(sys.argv[1:])
109
110
111 ### configure things ###
112
113 if options.disable_shuffle_demon:
114 random.shuffle_demon.enabled = False
115
116
117 def parse_plot_constraint(s):
118 from marysue.plot import get_plot_class
119 name, count = s.split(':')
120 return (get_plot_class(name), count)
121
122
123 plot_min = [parse_plot_constraint(s) for s in options.plot_min.split(',')] if options.plot_min else ()
124 plot_max = [parse_plot_constraint(s) for s in options.plot_max.split(',')] if options.plot_max else ()
125
126
127 generate_front_matter = True
128 if plot_min or plot_max:
129 generate_front_matter = False
130 chapters = (
131 {
132 'plot_min': plot_min,
133 'plot_max': plot_max,
134 'plot_depth': int(options.plot_depth),
135 },
136 )
137
138 ### do the generation! ###
139
140 novel = Novel(
141 chapters,
142 generate_front_matter=generate_front_matter,
143 synopsis=options.synopsis,
144 dump=options.dump
145 )
146
147 for n, chapter in enumerate(chapters):
148 plotter = Plotter(
149 serenity.protagonists,
150 serenity.antagonists,
151 serenity.goons,
152 serenity.macguffins,
153 serenity.settings,
154 )
155 novel.generate_chapter(n, plotter, **chapter)
156 if n % 6 == 5:
157 serenity.serenity.promote()
158
159 novel.trim()
160
161 ### publish
162
163 if options.publish:
164 novel.publish()
165 else:
166 print novel.text
0 h1 {
1 font-size: 300%;
2 text-align: center;
3 margin-left: 16%;
4 margin-right: 16%;
5 text-decoration: underline;
6 margin-bottom: 1em;
7 margin-top: 4em;
8 }
9
10 h2 {
11 font-size: 200%;
12 text-align: center;
13 margin-top: 8em;
14 margin-left: 16%;
15 margin-right: 16%;
16 }
17
18 h3 {
19 font-size: 200%;
20 text-align: center;
21 margin-bottom: 8em;
22 margin-left: 16%;
23 margin-right: 16%;
24 }
25
26 p {
27 font-size: 125%;
28 margin-left: 16%;
29 margin-right: 16%;
30 }
31
32 hr {
33 width: 33%;
34 margin-top: 3em;
35 margin-bottom: 3em;
36 }
37
38 ol {
39 font-size: 110%;
40 text-align: center;
41 }
(New empty file)
0 # encoding: UTF-8
1
2 """Abstract Story Trees.
3
4 """
5
6 import re
7
8 import marysue.util as random
9
10
11 ALIASES = {
12 'indef': 'indefinite',
13 'def': 'definite',
14 'obj': 'object',
15 'sub': 'subject',
16 'subj': 'subject',
17 'his': 'possessive_pronoun',
18 'her': 'possessive_pronoun',
19 'he': 'pronoun',
20 'she': 'pronoun',
21 }
22
23
24 class AST(object):
25 slots = None
26 templates = None
27
28 def __init__(self, *args, **kwargs):
29 self._children = args
30 if self.slots is not None:
31 for key, value in kwargs.iteritems():
32 if key not in self.slots:
33 raise AttributeError(
34 "{0} has no attribute '{1}'".format(
35 self.__class__.__name__, key
36 )
37 )
38 for key in self.slots:
39 kwargs.setdefault(key, None)
40 self._attrs = kwargs
41
42 def __repr__(self):
43 children = ', '.join([repr(c) for c in self._children])
44 attrs = ', '.join(['%s=%r' % (key, value) for key, value in self._attrs.iteritems() if value is not None])
45 j = ', ' if children and attrs else ''
46 return "%s(%s%s%s)" % (self.__class__.__name__, children, j, attrs)
47
48 def repr_abbrev(self):
49 attrs = ', '.join(['%s=%r' % (key, value) for key, value in self._attrs.iteritems() if value is not None])
50 return "%s(%s)" % (self.__class__.__name__, attrs)
51
52 def dump(self, f, indent=0):
53 f.write(' ' * indent)
54 f.write(self.repr_abbrev())
55 f.write('\n')
56 for child in self:
57 child.dump(f, indent + 2)
58
59 def __getitem__(self, index):
60 return self._children[index]
61
62 def __iter__(self):
63 return self._children.__iter__()
64
65 def __getattr__(self, name):
66 if name in self._attrs:
67 return self._attrs[name]
68 raise AttributeError(name)
69
70 def iteritems(self):
71 return self._attrs.iteritems()
72
73 def flatten(self):
74 """Individual nodes which you want to flatten must define how they are to be flattened"""
75 return self.__class__(*[c.flatten() for c in self], **dict(self.iteritems()))
76
77 def insert(self, position, *args, **kwargs):
78 """Returns a new AST node"""
79 children = [c for c in self]
80 for a in args:
81 children.insert(position, a)
82 position += 1
83 attrs = dict((k, v) for k, v in self.iteritems())
84 attrs.update(kwargs)
85 return self.__class__(*children, **attrs)
86
87 def render_t_impl(self, template):
88
89 def pick_one(match):
90 return random.choice(tuple(match.group(1).split('|')))
91 template = re.sub(r'\<(.*?)\>', pick_one, template)
92
93 def repl(match):
94 parts = [ALIASES.get(part, part) for part in match.group(1).split('.') if part]
95 try:
96 obj = self._attrs[parts.pop(0)]
97 except KeyError:
98 print self
99 raise
100 while parts:
101 obj = getattr(obj, parts.pop(0))
102 return obj
103 return re.sub(r'\{([a-zA-Z0-9_.]+)\}', repl, template)
104
105 def render_t(self, template):
106 try:
107 return self.render_t_impl(template)
108 except Exception:
109 print
110 print "!!! error in template '%s' on ast %r" % (template, self)
111 print
112 raise
113
114 def render(self):
115 if self.templates:
116 # TODO why is random.choice() not sufficient here?
117 template = random.shuffle_demon.choice(self.templates)
118 return self.render_t(template)
119 raise NotImplementedError(repr(self))
120
121 def __unicode__(self):
122 return self.render()
123
124
125 # TODO AST should inherit from this
126
127 class Properties(object):
128 """Please treat as immutable"""
129
130 slots = None
131
132 def __init__(self, **kwargs):
133 if self.slots is not None:
134 for key, value in kwargs.iteritems():
135 if key not in self.slots:
136 raise AttributeError(
137 "{0} has no attribute '{1}'".format(
138 self.__class__.__name__, key
139 )
140 )
141 for key in self.slots:
142 kwargs.setdefault(key, None)
143 self._attrs = kwargs
144
145 def __repr__(self):
146 attrs = ', '.join(['%s=%r' % (key, value) for key, value in self._attrs.iteritems() if value is not None])
147 return "%s(%s)" % (self.__class__.__name__, attrs)
148
149 def __getattr__(self, name):
150 if name in self._attrs:
151 return self._attrs[name]
152 raise AttributeError(name)
153
154 def __setattr__(self, name, value):
155 if name not in ('_attrs',):
156 raise AttributeError(name)
157 return super(Properties, self).__setattr__(name, value)
158
159 def iteritems(self):
160 return self._attrs.iteritems()
161
162 def clone(self, **kwargs):
163 attrs = self._attrs.copy()
164 attrs.update(kwargs)
165 return self.__class__(**attrs)
0 import marysue.util as random
1 from marysue.objects import Proper, MasculineMixin, FeminineMixin, Group
2
3
4 # - - - - ranks - - - -
5
6 RANKS = (
7 'Ensign',
8 'Lieutenant',
9 'Lieutenant Commander',
10 'Commander',
11 'Captain',
12 'Commodore',
13 'Admiral',
14 'Super Admiral',
15 )
16
17
18 class Character(Proper):
19 def __init__(self, names, **kwargs):
20 super(Character, self).__init__(names, **kwargs)
21 self.stature = random.choice((
22 'somewhat short',
23 'of average height',
24 'rather tall',
25 ))
26 self.hair_length = random.choice((
27 'long',
28 'shoulder length',
29 'short',
30 'close cropped',
31 ))
32 self.hair_colour = random.choice((
33 'blonde', 'brown', 'red', 'auburn', 'black',
34 ))
35 self.eye_colour = random.choice((
36 'brown', 'blue', 'grey', 'green', 'hazel',
37 ))
38 self.war_cry = "BY THE " + random.choice((
39 'MOONS',
40 'RINGS',
41 'MOUNTAINS',
42 'METEORS',
43 )) + " OF " + random.choice((
44 'VENUS',
45 'MARS',
46 'JUPITER',
47 'NEPTUNE',
48 ))
49
50 @classmethod
51 def characters_to_set(cls, *args):
52 """Not the most intuitive place for this method? Oh well."""
53 s = set()
54 for arg in args:
55 if arg is None:
56 continue
57 if isinstance(arg, Group):
58 for c in arg:
59 s.add(c)
60 else:
61 s.add(arg)
62 return set([p for p in s if isinstance(p, cls)])
63
64 def promote(self):
65 """Mutates this character. One of the few methods that'll do that."""
66 self.rank = self.next_rank
67
68 @property
69 def next_rank(self):
70 for n, r in enumerate(RANKS):
71 if r == self.rank:
72 return RANKS[n + 1]
73
74 @property
75 def costume_materials(self):
76 return (
77 'silk', 'leather',
78 'polyester', 'cotton', 'nylon', 'denim',
79 'rayon', 'dacron', 'crinkly foil',
80 # 'woolen' is far too weird for most things, esp. footwear
81 # 'suede' is likewise a little weird
82 )
83
84 @property
85 def costume_decorations(self):
86 return (
87 ' with {colour} stripes',
88 ' with {colour} {costume_material} trim',
89 )
90
91 @property
92 def costume_decoration(self):
93 if random.chance(75):
94 return ''
95 return random.choice(self.costume_decorations).format(
96 colour=self.colour,
97 costume_material=random.choice(self.costume_materials),
98 )
99
100 @property
101 def costume_adjectives(self):
102 return (
103 'fine',
104 'snappy',
105 'handsome',
106 )
107
108 @property
109 def costume_adjective(self):
110 if random.chance(60):
111 return ''
112 else:
113 return random.choice(self.costume_adjectives)
114
115 @property
116 def wearing(self):
117 return random.choice((
118 'wearing', 'sporting', 'looking fine in',
119 'looking smashing in', 'looking delightful in',
120 'looking impressive in', 'decked out in'
121 ))
122
123 @property
124 def yet(self):
125 return '{} yet {}'.format(
126 random.choice((
127 'smooth', 'graceful', 'gentle', 'supple', 'soft', 'exquisite',
128 )),
129 random.choice((
130 'powerful', 'forceful', 'firm', 'masterful', 'confident', 'strong',
131 ))
132 )
133
134 @property
135 def motion(self):
136 if random.chance(80):
137 return ''
138 return 'with a {} motion, '.format(self.yet)
139
140 @property
141 def simile(self):
142 return 'like {} {}'.format(
143 random.choice((
144 'a tiger',
145 'charcoal',
146 'an elephant',
147 'a ninja',
148 'a gangster',
149 'a giraffe',
150 'a hurricane',
151 'a samurai',
152 'a rabid dog',
153 'a crazed bull',
154 'a baboon',
155 'a gorilla',
156 )),
157 random.choice((
158 'in a snowstorm',
159 'in a rainstorm',
160 'piloting a helicopter',
161 'driving a race car',
162 'in a gymnasium parking lot',
163 'in a pet shop',
164 'in a bazaar',
165 'in a video arcade',
166 'at a baseball game',
167 'in a performance art piece',
168 'in a jewellery store',
169 'in a mosh pit',
170 'at a square dance',
171 'at a monster truck rally',
172 ))
173 )
174
175 @property
176 def withvoice(self):
177 return 'with a voice ' + self.simile
178
179
180 class MasculineCharacter(MasculineMixin, Character):
181 def __init__(self, names, **kwargs):
182 super(MasculineCharacter, self).__init__(names, **kwargs)
183 self.feature_adj = random.choice((
184 'strong', 'deep', 'wide', 'large',
185 ))
186 self.feature = random.choice((
187 'nose', 'mouth', 'brow', 'chin',
188 ))
189
190
191 class FeminineCharacter(FeminineMixin, Character):
192 def __init__(self, names, **kwargs):
193 super(FeminineCharacter, self).__init__(names, **kwargs)
194 self.feature_adj = random.choice((
195 'small', 'perky', 'narrow', 'large',
196 ))
197 self.feature = random.choice((
198 'nose', 'mouth', 'forehead', 'chin',
199 ))
200
201
202 class MarySue(FeminineCharacter):
203 def __init__(self, names, **kwargs):
204 super(MarySue, self).__init__(names, **kwargs)
205 self.feature_adj = random.choice((
206 'wicked cute', 'perfect', 'beautiful', 'enchanting',
207 ))
208 self.feature = random.choice((
209 'nose', 'mouth', 'forehead', 'chin',
210 ))
211 self.stature = random.choice((
212 'enchantingly petite',
213 'a bit on the short side, but in a cute way',
214 'neither short nor tall but not average either',
215 'sort of tall for a girl, but not in a bad way',
216 ))
217 self.hair_length = random.choice((
218 'exceptionally long (down to her knees)',
219 'wicked long (down to her knees)',
220 'beautiful long',
221 'perky shoulder length',
222 ))
223 self.hair_colour = random.choice((
224 'purple', 'indigo', 'violet', 'midnight black',
225 'black and purple',
226 'multi coloured', 'rainbow coloured', 'multi hued',
227 'rainbow hued', 'shimmering rainbow coloured',
228 'shimmering multi hued', 'shimmering rainbow hued',
229 ))
230 self.eye_colour = random.choice((
231 'purple', 'indigo', 'violet', 'icy blue',
232 'multi coloured', 'rainbow coloured', 'multi hued', 'rainbow hued',
233 'shimmering multi coloured', 'shimmering rainbow coloured', 'shimmering multi hued', 'shimmering rainbow hued',
234 'kaleidoscope coloured', 'shimmering kaleidoscope coloured',
235 ))
236
237 @property
238 def colours(self):
239 return (
240 'purple', 'dark purple', 'pale purple',
241 'violet', 'dark violet', 'pale violet',
242 'indigo', 'dark indigo', 'pale indigo',
243 'red and purple', 'purple and gold', 'black and purple',
244 'crimson', 'crimson and purple', 'crimson and violet',
245 'purple and white', 'purple and violet', 'blue and purple',
246 'midnight black', 'white',
247
248 'multi coloured', 'rainbow coloured', 'multi hued',
249 'rainbow hued', 'shimmering rainbow coloured',
250 'shimmering multi hued', 'shimmering rainbow hued',
251
252 'gold coloured', 'silver coloured', 'silvery',
253 'silver and purple', 'shiny silver', 'shiny purple',
254 'deep violet', 'deep indigo', 'shimmering purple',
255 )
256
257 @property
258 def costume_decorations(self):
259 return (
260 ' with frilly {colour} lace',
261 ' with {colour} lacy frills',
262 ' with {colour} lightning bolt patterns',
263 ' with {colour} star patterns',
264 ' with {colour} moon beam patterns',
265 ' adorned with {gems}',
266 ' with {colour} {costume_material} trim',
267 )
268
269 @property
270 def costume_decoration(self):
271 return random.choice(self.costume_decorations).format(
272 colour=self.colour,
273 costume_material=random.choice(self.costume_materials),
274 gems=random.choice((
275 'jewels', 'gems', 'rubies', 'emeralds', 'sapphires',
276 )),
277 )
278
279 @property
280 def costume_adjectives(self):
281 return (
282 '',
283 'beautiful',
284 'elegant',
285 'exquisite',
286 'fantastic',
287 'marvellous',
288 )
289
290 @property
291 def costume_adjective(self):
292 if random.chance(4):
293 return 'WICKED AWESOME'
294 else:
295 return random.choice(self.costume_adjectives)
296
297
298 class DreamBoat(MasculineCharacter):
299 def __init__(self, names, **kwargs):
300 super(DreamBoat, self).__init__(names, **kwargs)
301 self.feature_adj = random.choice((
302 'handsome', 'perfect', 'beautiful', 'enthralling',
303 ))
304 self.feature = random.choice((
305 'nose', 'mouth', 'brow', 'chin',
306 ))
307 self.stature = random.choice((
308 'a bit on the short side, but in a cute way',
309 'of perfectly normal height like a normal person should be',
310 'quite tall, but really handsomely tall, not freakishly tall',
311 ))
312
313
314 class Rival(FeminineCharacter):
315 @property
316 def wearing(self):
317 return random.choice((
318 'wearing', 'looking frumpy in',
319 'looking tastless in', 'looking completely unimpressive in',
320 'gotten up in',
321 ))
322
323 @property
324 def costume_decorations(self):
325 return (
326 ' with gaudy {colour} polka dots',
327 ' with a tacky {colour} zig zag pattern',
328 )
329
330 @property
331 def costume_adjectives(self):
332 return (
333 '',
334 'ill fitting',
335 'tacky',
336 'tasteless',
337 'gaudy',
338 'bleak',
339 'outdated',
340 )
341
342
343 class TheOptimist(MasculineCharacter):
344 @property
345 def wearing(self):
346 return random.choice((
347 'wearing', 'sporting', 'looking exciting in',
348 'resplendent in', 'looking ready for action in',
349 ))
350
351
352 class BaddieMixin(object):
353 @property
354 def wearing(self):
355 return random.choice((
356 'wearing', 'looking menacing in',
357 'looking villanous in', 'looking impressive in',
358 'gotten up in', 'looking intimidating in',
359 ))
360
361 @property
362 def colours(self):
363 return (
364 'black', 'midnight black', 'dark black', 'deep black',
365 'red', 'dark red', 'blood red', 'blood coloured', 'deep red',
366 )
367
368
369 class BadGuy(BaddieMixin, MasculineCharacter):
370 def __init__(self, names, **kwargs):
371 super(BadGuy, self).__init__(names, **kwargs)
372 self.feature_adj = random.choice((
373 'ugly', 'jutting', 'overbearing', 'scarred',
374 ))
375 self.feature = random.choice((
376 'nose', 'mouth', 'brow', 'chin',
377 ))
378 self.hair_colour = random.choice((
379 'black', 'platinum blonde', 'white', 'green',
380 ))
381
382
383 class BadGal(BaddieMixin, FeminineCharacter):
384 def __init__(self, names, **kwargs):
385 super(BadGal, self).__init__(names, **kwargs)
386 self.feature_adj = random.choice((
387 'ugly', 'jutting', 'overbearing', 'scarred',
388 ))
389 self.feature = random.choice((
390 'nose', 'mouth', 'brow', 'chin',
391 ))
392 self.hair_colour = random.choice((
393 'black', 'platinum blonde', 'white', 'green',
394 ))
0 import marysue.util as random
1 from marysue.objects import Object, Plural, MasculineMixin, FeminineMixin
2
3
4 # since the first argument is always a Character, arguably, these
5 # functions should all be methods on Character instead. Oh well.
6
7
8 def make_costume(character, item_choices):
9 adjective = character.costume_adjective
10 if adjective:
11 adjective = adjective + ' '
12 colour = character.colour
13 material = random.choice(character.costume_materials)
14 (item, cls_) = random.choice(item_choices)
15 with_ = character.costume_decoration
16
17 names = (
18 '{}{} {} {}{}'.format(adjective, colour, material, item, with_),
19 item
20 )
21
22 return cls_(names=names)
23
24
25 def make_torso_costume(character):
26 item_choices = (
27 'jacket', 'shirt', 'jerkin', 'top', 'jersey', 'suit jacket',
28 'sweater', 'hoodie', 'jumper',
29 )
30
31 if isinstance(character, FeminineMixin):
32 item_choices += ('blouse', 'halter top', 'tank top', 'frock',)
33
34 if isinstance(character, MasculineMixin):
35 item_choices += ('muscle shirt',)
36
37 return make_costume(character, tuple((item, Object) for item in item_choices))
38
39
40 def make_legs_costume(character):
41 item_choices = tuple((item, Plural) for item in (
42 'trousers', 'leggings', 'slacks', 'culottes',
43 # 'hose',
44 ))
45
46 if isinstance(character, FeminineMixin):
47 item_choices += (('skirt', Object),)
48
49 return make_costume(character, item_choices)
50
51
52 def make_onesie_costume(character):
53 item_choices = tuple((item, Object) for item in (
54 'jumpsuit', 'track suit', 'robe', 'smock',
55 'long coat', 'trench coat', 'great coat',
56 )) + (('coveralls', Plural),)
57
58 if isinstance(character, FeminineMixin):
59 item_choices += tuple((item, Object) for item in (
60 'dress', 'gown', 'leotard',
61 ))
62
63 return make_costume(character, item_choices)
64
65
66 def make_feet_costume(character):
67 item_choices = tuple((item, Plural) for item in (
68 'boots', 'shoes', 'sandals', 'sneakers', 'trainers',
69 'dress shoes'
70 ))
71
72 if isinstance(character, FeminineMixin):
73 item_choices += (('pixie boots', Plural), ('pumps', Plural))
74
75 return make_costume(character, item_choices)
0 from marysue.objects import Object
1
2
3 class Duty(Object):
4 pass
5
6
7 class RescueDuty(Duty):
8 def __init__(self, object, **kwargs):
9 name = 'rescue ' + object.name
10 super(RescueDuty, self).__init__((name,), **kwargs)
11
12
13 class RetrieveDuty(Duty):
14 def __init__(self, object, **kwargs):
15 name = 'retrieve ' + object.definite
16 super(RetrieveDuty, self).__init__((name,), **kwargs)
0 import sys
1
2 import marysue.util as random
3 from marysue.objects import Object
4 from marysue.characters import Character, MarySue, TheOptimist
5 from marysue.storytree import Story, Scene, EventSequence, Paragraph
6 from marysue.events import *
7 from marysue.state import State
8 from marysue.costume import (
9 make_torso_costume, make_legs_costume,
10 make_onesie_costume, make_feet_costume
11 )
12
13
14 def edit_story(story, introduced, **kwargs):
15 """Standard story-revising pipeline. Takes a basic Story
16 that was generated from a plot and returns a Story that is
17 closer to something that you can actually read.
18
19 `introduced` is a set of objects that have already been
20 introduced in previous stories and that need no introduction
21 here. Objects will be added to it as they are introduced in
22 this story.
23 """
24
25 ### Massage the generated story ###
26
27 story = merge_adjacent_scenes(story)
28 story = resolve_setting_references(story)
29
30 ### Initialize story instant-states and context ###
31
32 all_objects = set()
33 collect_objects(story, all_objects)
34 story = assign_empty_states(story, objects=all_objects)
35
36 ### Assign locations ###
37
38 story = assign_locations(story)
39
40 ### Assign moods and duties ###
41
42 story = assign_moods(story)
43 story = remove_mood_modifier_events(story)
44
45 story = assign_duties(story)
46 story = remove_duty_acquisition_events(story)
47
48 ### Elaborate the story ###
49
50 story = describe_scene(story)
51
52 story = assign_costumes(story)
53 story = describe_costumes(story)
54
55 newly_introduced = set()
56 story = describe_characters(story, introduced, newly_introduced)
57 reminded = set(newly_introduced)
58 story = remind_characters(story, reminded)
59
60 ### Diction ###
61
62 story = assign_first_occurrence(story)
63
64 story = story.flatten()
65
66 story = split_into_paragraphs(story)
67 story = assign_referents(story)
68 story = conjoin_sentences(story)
69
70 return story
71
72
73 # - - - - for debugging - - -
74
75
76 def show_state(ast, attr, cls):
77 """Example usage: show_state(story, 'subject', StateDutyEvent)"""
78 for child in ast:
79 show_state(child, cls)
80 if isinstance(ast, cls):
81 print "%s state is: %r" % (attr, getattr(ast, attr))
82 print
83
84
85 # - - - - editor stages - - - -
86
87
88 def collect_objects(ast, object_set):
89 """Places all the objects found in the given story tree into the given
90 object_set. Does not return anything, modifies object_set instead."""
91 for child in ast:
92 collect_objects(child, object_set)
93 for key, value in ast.iteritems():
94 if isinstance(value, Object):
95 object_set.add(value)
96
97
98 def assign_empty_states(ast, context=None, objects=None):
99 """Replaces all Object references in a story tree with State objects
100 which proxy for those Objects. These State objects are initially
101 empty; further passes will make them reflect what's actually going on."""
102
103 if context is None:
104 context = dict((object, State(object)) for object in objects)
105
106 attrs = {}
107 for key, value in ast.iteritems():
108 if isinstance(value, Object):
109 attrs[key] = context[value]
110 else:
111 attrs[key] = value
112 return ast.__class__(*[assign_empty_states(c, context=context) for c in ast], **attrs)
113
114
115 def assign_locations(ast, context=None):
116 # And this is entirely so we can say "Her scarf shone in the dim light of the tunnel!"
117
118 if context is None:
119 context = {}
120
121 if isinstance(ast, Scene):
122 context['location'] = ast.setting
123
124 attrs = {}
125 for role, state in ast.iteritems():
126 if isinstance(state, State) and isinstance(state.object, Character):
127 state = state.clone(location=context['location'])
128 attrs[role] = state
129
130 return ast.__class__(*[assign_locations(c, context=context) for c in ast], **attrs)
131
132
133 def assign_costumes(ast, context=None):
134 if context is None:
135 context = {}
136
137 # reset costumes in each scene
138 if isinstance(ast, Scene):
139 context = {}
140
141 attrs = {}
142 for role, state in ast.iteritems():
143 if isinstance(state, State) and isinstance(state.object, Character):
144 character = state.object
145 if state.object not in context:
146 context[character] = {
147 'feet': make_feet_costume(character),
148 }
149 if random.chance(66):
150 context[character].update({
151 'torso': make_torso_costume(character),
152 'legs': make_legs_costume(character)
153 })
154 else:
155 context[character].update({
156 'torso': make_onesie_costume(character),
157 'legs': None
158 })
159
160 entry = context[character]
161 state = state.clone(
162 torso_costume=entry['torso'],
163 legs_costume=entry['legs'],
164 feet_costume=entry['feet'],
165 )
166 attrs[role] = state
167
168 return ast.__class__(*[assign_costumes(c, context) for c in ast], **attrs)
169
170
171 def assign_moods(ast, moods=None):
172 if moods is None:
173 moods = {}
174
175 if isinstance(ast, MoodModifierEvent):
176 moods[ast.subject.object] = ast.mood()
177
178 attrs = {}
179 for role, state in ast.iteritems():
180 if isinstance(state, State) and isinstance(state.object, Character):
181 character = state.object
182 if character not in moods:
183 print "ERROR", character, "appears before mood assigned, assuming happy"
184 moods[character] = 'happy'
185 if isinstance(character, TheOptimist):
186 # No, I'm not going to let it get me down!
187 moods[character] = 'happy'
188 state = state.clone(mood=moods[character])
189 attrs[role] = state
190
191 return ast.__class__(*[assign_moods(c, moods=moods) for c in ast], **attrs)
192
193
194 def remove_mood_modifier_events(ast):
195 children = []
196 for child in ast:
197 if isinstance(child, MoodModifierEvent):
198 if random.chance(10) and isinstance(child.subject.object, TheOptimist) and child.mood() != 'happy':
199 children.append(CharacterStaysHappyEvent(subject=child.subject))
200 else:
201 children.append(remove_mood_modifier_events(child))
202
203 return ast.__class__(*children, **dict(ast.iteritems()))
204
205
206 def assign_duties(ast, duties=None):
207 if duties is None:
208 duties = {}
209
210 if isinstance(ast, AcquireDutyEvent):
211 duties.setdefault(ast.subject.object, set()).add(ast.object.object)
212
213 if isinstance(ast, RelieveDutyEvent):
214 duties.setdefault(ast.subject.object, set())
215 if ast.object.object not in duties[ast.subject.object]:
216 print >>sys.stderr, '%r not in %r`s %r' % (
217 ast.object.object, ast.subject.object, duties[ast.subject.object]
218 )
219 else:
220 duties[ast.subject.object].remove(ast.object.object)
221
222 attrs = {}
223 for role, state in ast.iteritems():
224 if isinstance(state, State) and isinstance(state.object, Character):
225 character = state.object
226 if character not in duties:
227 duties[character] = set()
228 state = state.clone(duties=set(duties[character]))
229 attrs[role] = state
230
231 return ast.__class__(*[assign_duties(c, duties=duties) for c in ast], **attrs)
232
233
234 def remove_duty_acquisition_events(ast):
235 children = []
236 for child in ast:
237 if not isinstance(child, (AcquireDutyEvent, RelieveDutyEvent)):
238 children.append(remove_duty_acquisition_events(child))
239
240 return ast.__class__(*children, **dict(ast.iteritems()))
241
242
243 def assign_referents(ast, context=None):
244 if context is None:
245 context = {'referent': None}
246
247 # reset referent in each... eventually this will be paragraph
248 #if isinstance(ast, Turn):
249 # context['referent'] = None
250
251 attrs = {}
252 for role, state in ast.iteritems():
253 if isinstance(state, State):
254 attrs[role] = state.clone(is_referent=(state.object == context['referent']))
255 if role == 'subject':
256 context['referent'] = state.object
257 else:
258 attrs[role] = state
259
260 return ast.__class__(*[assign_referents(c, context) for c in ast], **attrs)
261
262
263 def resolve_setting_references(ast, setting=None):
264 if isinstance(ast, Scene):
265 setting = ast.setting
266
267 attrs = dict((k, v) for (k, v) in ast.iteritems())
268
269 if isinstance(ast, PoseDescription):
270 attrs['object'] = State(setting.nearby_scenery)
271
272 return ast.__class__(*[resolve_setting_references(c, setting=setting) for c in ast], **attrs)
273
274
275 def assign_first_occurrence(ast, occurred=None):
276 """We use this to select between definite and indefinite article"""
277 # TODO: mentioning in dialogue does not count as first occurrence
278 if occurred is None:
279 occurred = set()
280
281 attrs = {}
282 for role, state in ast.iteritems():
283 # first of these is to avoid counting objects in Scene, etc as occurrence
284 if isinstance(ast, Event) and isinstance(state, State):
285 if state.object not in occurred:
286 state = state.clone(first_occurrence=True)
287 occurred.add(state.object)
288 attrs[role] = state
289
290 return ast.__class__(*[assign_first_occurrence(c, occurred=occurred) for c in ast], **attrs)
291
292
293 def describe_scene(ast):
294 children = [c for c in ast]
295
296 if isinstance(ast, Scene):
297 children = [EventSequence(
298 SettingDescription(
299 subject=ast.setting
300 ),
301 NearbyDescription(
302 subject=ast.setting,
303 object=State(
304 object=ast.setting.nearby_scenery,
305 location=ast.setting
306 )
307 ),
308 GenericSettingDescription(
309 subject=ast.setting,
310 ),
311 EventSequence(*children)
312 )]
313
314 return ast.__class__(*[describe_scene(c) for c in children], **dict(ast.iteritems()))
315
316
317 def describe_characters(ast, described, newly_introduced):
318 """Describes them as if we are meeting them for the first time.
319 `described` is a set of characters who have already been described.
320
321 `described` persists across several stories, but anyone we do
322 describe here, we put in newly_introduced (it's essentially output
323 only) so that we can tell not to e.g. remind the reader of their
324 appearance overmuch.
325 """
326
327 if isinstance(ast, Event):
328 if isinstance(ast.subject, State) and isinstance(ast.subject.object, Character):
329 if ast.subject.object not in described:
330 if isinstance(ast.subject.object, MarySue) or random.chance(50):
331 described.add(ast.subject.object)
332 newly_introduced.add(ast.subject.object)
333 return EventSequence(
334 ast,
335 CharacterDescription(subject=ast.subject),
336 CharacterFeaturesDescription(subject=ast.subject)
337 )
338
339 return ast.__class__(*[describe_characters(c, described, newly_introduced) for c in ast], **dict(ast.iteritems()))
340
341
342 def remind_characters(ast, reminded):
343 """Describes them assuming we have already been introduced to them,
344 by subtly (hah) reminding us about what they look like."""
345
346 if isinstance(ast, Event):
347 if isinstance(ast.subject, State) and isinstance(ast.subject.object, Character):
348 if ast.subject.object not in reminded:
349 if random.chance(20):
350 reminded.add(ast.subject.object)
351 return EventSequence(
352 ast,
353 CharacterReminder(subject=ast.subject),
354 )
355
356 return ast.__class__(*[remind_characters(c, reminded) for c in ast], **dict(ast.iteritems()))
357
358
359 def describe_costumes(ast, described=None):
360 if described is None:
361 described = set()
362
363 if isinstance(ast, Scene):
364 described = set()
365
366 if isinstance(ast, Event):
367 if isinstance(ast.subject, State) and isinstance(ast.subject.object, Character):
368 if ast.subject.object not in described:
369 if isinstance(ast.subject.object, MarySue) or random.chance(50):
370 described.add(ast.subject.object)
371 return EventSequence(
372 ast,
373 TorsoCostumeReminder(subject=ast.subject) if random.chance(10) else TorsoCostumeDescription(subject=ast.subject),
374 FeetCostumeReminder(subject=ast.subject) if random.chance(10) else FeetCostumeDescription(subject=ast.subject)
375 )
376
377 return ast.__class__(*[describe_costumes(c, described=described) for c in ast], **dict(ast.iteritems()))
378
379
380 def collect_objects_from_states(ast, object_set):
381 for child in ast:
382 collect_objects_from_states(child, object_set)
383 for key, value in ast.iteritems():
384 if isinstance(value, State):
385 object_set.add(value.object)
386
387
388 def add_crickets(ast):
389 if isinstance(ast, LookAtEvent):
390 return EventSequence(ast, CricketsEvent())
391
392 return ast.__class__(*[add_crickets(c) for c in ast], **dict(ast.iteritems()))
393
394
395 def merge_adjacent_scenes(ast):
396 children = []
397 for child in ast:
398 if isinstance(child, Scene) and children and isinstance(children[-1], Scene) and child.setting == children[-1].setting:
399 new_scene_contents = [gc for gc in children[-1]] + [gc for gc in child]
400 # TODO: what if the Scenes differ in OTHER attributes?
401 # for now there are none (assume characters are assigned later on)
402 children[-1] = Scene(*new_scene_contents, **dict(children[-1].iteritems()))
403 else:
404 # note that this does not recurse
405 children.append(child)
406
407 return ast.__class__(*children, **dict(ast.iteritems()))
408
409
410 def split_into_paragraphs(ast):
411 if isinstance(ast, Scene):
412 assert len([c for c in ast]) == 1
413 eseq = ast[0]
414 assert isinstance(eseq, EventSequence)
415
416 children = []
417 parachilds = []
418 subject = None
419 for child in eseq:
420 assert isinstance(child, Event), repr(child)
421 if child.new_para or (isinstance(child.subject, State) and child.subject.object != subject):
422 if parachilds:
423 children.append(parachilds)
424 parachilds = []
425 if child.subject:
426 subject = child.subject.object
427 else:
428 subject = None
429 elif not isinstance(child.subject, State):
430 if parachilds:
431 children.append(parachilds)
432 parachilds = []
433 subject = None
434 parachilds.append(child)
435 if parachilds:
436 children.append(parachilds)
437
438 children = [EventSequence(*[Paragraph(*r) for r in children])]
439 else:
440 children = [split_into_paragraphs(c) for c in ast]
441
442 return ast.__class__(*children, **dict(ast.iteritems()))
443
444
445 def conjoin_sentences(ast):
446 if isinstance(ast, Paragraph):
447 children = []
448 for child in ast:
449 if not children or \
450 not child.is_conjoinable or \
451 isinstance(children[-1], ConjoinedEvent) or \
452 random.chance(100): # DISABLED because for now it's kind of awful to read
453 children.append(child)
454 else:
455 last = children[-1]
456 compound = ConjoinedEvent(event1=last, event2=child)
457 children[-1] = compound
458 else:
459 children = [conjoin_sentences(c) for c in ast]
460
461 return ast.__class__(*children, **dict(ast.iteritems()))
0 import marysue.util as random
1 from marysue.ast import AST
2
3
4 # - - - -
5
6
7 class Event(AST):
8 exciting = False
9 new_para = False
10 slots = (
11 'subject', 'object',
12 'object2', # rarely used
13 )
14
15 @property
16 def is_conjoinable(self):
17 if not self.templates:
18 return False
19 if any([t[0] in ('"', "'") for t in self.templates]):
20 return False
21 return True
22
23
24 # - - - - mood modifier events
25
26
27 class MoodModifierEvent(Event):
28 def __init__(self, subject, **kwargs):
29 # this is just to debug where we might be constructing it with wrong args
30 assert subject is not None
31 super(MoodModifierEvent, self).__init__(subject=subject, **kwargs)
32
33 def mood(self):
34 raise NotImplementedError
35
36
37 class BecomeHappyEvent(MoodModifierEvent):
38 templates = (
39 '{subj.pronoun} became HAPPY',
40 )
41
42 def mood(self):
43 return 'happy'
44
45
46 class BecomeSadEvent(MoodModifierEvent):
47 templates = (
48 '{subj.pronoun} became SAD',
49 )
50
51 def mood(self):
52 return 'sad'
53
54
55 class BecomeAngryEvent(MoodModifierEvent):
56 templates = (
57 '{subj.pronoun} became ANGRY',
58 )
59
60 def mood(self):
61 return 'angry'
62
63
64 class BecomeEmbarrassedEvent(MoodModifierEvent):
65 templates = (
66 '{subj.pronoun} became EMBARRASSED',
67 )
68
69 def mood(self):
70 return 'embarrassed'
71
72
73 # - - - - duty acquisition events. object is the duty acquired, subject is the character acquiring it
74
75
76 class AcquireDutyEvent(Event):
77 templates = (
78 '{subj.pronoun} acquired DUTY `{obj.name}`',
79 )
80
81
82 class RelieveDutyEvent(Event):
83 templates = (
84 '{subj.pronoun} was released from DUTY `{obj.name}`',
85 )
86
87
88 # - - - - actions and affect
89
90
91 class PickUpEvent(Event):
92 templates = (
93 '{subj.motion}{subj.pronoun} picked up {obj.pronoun} that {obj.was} nearby',
94 )
95
96
97 class HoldEvent(Event):
98 templates = (
99 '{subj.pronoun} held {obj.pronoun} in {subj.possessive} hand',
100 '{subj.motion}{subj.pronoun} lifted {obj.pronoun}< to eye level| into the light|>',
101 )
102
103
104 class ContemplateEvent(Event):
105 templates = (
106 '"{obj.proximal} represents how I feel inside," {subj.pronoun} {subj.said} {subj.adverb}',
107 )
108
109
110 class ApproachEvent(Event):
111 templates = (
112 '{subj.motion}{subj.pronoun} walked towards {obj.accusative}',
113 '{subj.motion}{subj.pronoun} took two steps in {obj.possessive} direction, then stopped',
114 )
115
116
117 class GestureAtEvent(Event):
118 templates = (
119 '{subj.motion}{subj.pronoun} <pointed|gestured> towards {obj.accusative}',
120 '{subj.pronoun} moved {subj.her} {subj.yet} <arm|hand> in the direction of {obj.accusative}',
121 )
122
123
124 class LookAtEvent(Event):
125 templates = (
126 '{subj.pronoun} looked at {obj.accusative}',
127 '{subj.pronoun} glanced {subj.adverb} at {obj.accusative}',
128 '{subj.pronoun} glared in the direction of {obj.accusative}',
129 )
130
131
132 class LookAroundEvent(Event):
133 templates = (
134 '{subj.pronoun} looked around',
135 '{subj.pronoun} surveyed the area<| with {subj.her} eyes>',
136 )
137
138
139 class PunchPalmWithFistEvent(Event):
140 # NOTUSED
141 templates = (
142 '{subj.pronoun} made a fist and punched {subj.his} other palm with {subj.his} fist',
143 )
144
145
146 class RepeatForEmphasisEvent(Event):
147 # NOTUSED
148 templates = (
149 '{subj.pronoun} did this <again|a second time>, for emphasis',
150 )
151
152
153 class EmoteEvent(Event):
154 templates = (
155 '{subj.pronoun} {subj.emoted} {subj.adverb}',
156 )
157
158
159 class CackleEvent(Event):
160 templates = (
161 '{subj.pronoun} cackled <evilly|wildly|maniacally|despicably|wickedly|hatefully|disdainfully>',
162 )
163
164
165 class StateDutyEvent(Event):
166 templates = (
167 '"We have a duty to {subj.pick_duty.name}!" {subj.said} {subj.pronoun} {subj.adverb}',
168 )
169
170
171 class GreetEvent(Event):
172 templates = (
173 '"Hello, {obj.def}," {subj.said} {subj.pronoun} {subj.adverb}',
174 '"Hello, {obj.def}," {subj.said} {subj.pronoun} with an enigmatic twitch of {sub.his} <nose|mouth|ears>',
175 )
176
177
178 class CharacterStaysHappyEvent(Event):
179 templates = (
180 'this was bad, but {subj.pronoun} didn`t let it <get {subj.accusative_pronoun} down|affect {subj.his} mood|cramp {subj.his} style|dampen {subj.his} cheer>',
181 '{subj.pronoun} wasn`t going to let a little thing like this <get {subj.accusative_pronoun} down|affect {subj.his} mood|cramp {subj.his} style|dampen {subj.his} cheer>, though',
182 '{subj.pronoun} gritted {subj.his} teeth and determined to stay <cheerful|upbeat|chipper|positive|optimistic>',
183 )
184
185
186 # - - - - events for chekov's gun - - - -
187
188
189 class IntroduceItemEvent(Event):
190 templates = (
191 '{subj.pronoun} was cracking open <walnuts|pecans> with {obj.pronoun}',
192 '{subj.pronoun} was <studiously|meticulously|intently> <polishing|cleaning|buffing> {obj.pronoun}',
193 )
194
195
196 class AskAboutItemEvent(Event):
197 templates = (
198 '"{obj.distal} means a lot to you, doesn`t it?" asked {subj.pronoun}',
199 '"{obj.distal}, it`s <kind of|pretty> special, isn`t it?" asked {subj.pronoun}',
200 )
201
202
203 class ReplyAboutItemEvent(Event):
204 templates = (
205 '"It means a lot to all of us, {obj.def}," {subj.pronoun} {subj.said} {subj.adverb}',
206 '"Space Fighters Command doesn`t entrust us with just any old thing, {obj.def}," {subj.pronoun} {subj.said} {subj.adverb}',
207 '"It`s not the <sort|kind|type> of thing you can just order <on|off|from> Omnizon, {obj.def}," {subj.pronoun} {subj.said} {subj.adverb}',
208 )
209
210
211 # - - - - events for cave-in plot - - - -
212
213
214 class RumblingSoundEvent(Event):
215 templates = (
216 'there was a rumbling sound',
217 )
218
219
220 class WhatWasThatNoiseEvent(Event):
221 templates = (
222 '"What was that noise?" {subj.said} {subj.pronoun}',
223 '"Did you hear something?" {subj.pronoun} {subj.said}',
224 )
225
226
227 class CaveInEvent(Event):
228 # FIXME subj.pronoun works badly here :/
229 exciting = True
230 templates = (
231 'suddenly, {subj.def} caved in',
232 'without warning, with a <tremendous|stupendous|overwhelming> <crash|boom|bang>, {subj.def} caved in',
233 'too quickly for anyone to react, {subj.def} caved in',
234 )
235
236
237 class StunnedEvent(Event):
238 templates = (
239 '{subj.pronoun} just stared, seemingly paralyzed',
240 '{subj.pronoun} stood frozen like a deer in headlights',
241 )
242
243
244 class BuriedUnderRubbleEvent(Event):
245 templates = (
246 '{subj.pronoun} was buried under rubble',
247 )
248
249
250 class DigOutEvent(Event):
251 templates = (
252 '{subj.pronoun} helped dig {obj.pronoun} out from the rubble',
253 )
254
255
256 # - - - - events for kidnapping plot - - - -
257
258
259 class AppearEvent(Event):
260 exciting = True
261 templates = (
262 '<then|suddenly|all of a sudden>, out of <nowhere|thin air>, {subj.pronoun} appeared',
263 )
264
265
266 class DisappearEvent(Event):
267 templates = (
268 'in a flash, {subj.pronoun} disappeared into thin air, taking {obj.pronoun} with {subj.accusative}',
269 )
270
271
272 class NoticeAntagonistEvent(Event):
273 templates = (
274 '"<It`s |>YOU!" {subj.shouted} {subj.pronoun}',
275 '"<It`s |>{obj.def}!" {subj.pronoun} {subj.shouted}',
276 )
277
278
279 class AntagonistBanterEvent(Event):
280 templates = (
281 '"I have you now!" {subj.said} {subj.pronoun} {subj.withvoice}',
282 '"What have we here!" {subj.said} {subj.pronoun} {subj.withvoice}',
283 '"We meet again, <kiddies|chums|do-gooders>!" {subj.said} {subj.pronoun} {subj.withvoice}',
284 '"So sorry to spoil your <little|> party, <kiddies|chums|do-gooders>!" {subj.said} {subj.pronoun} {subj.withvoice}',
285 )
286
287
288 class AbductEvent(Event):
289 templates = (
290 '{subj.pronoun} <grabbed|snatched> {obj.pronoun} from behind',
291 )
292
293
294 class WeMustFindThemEvent(Event):
295 templates = (
296 '"We must find out where {obj.pronoun} is being held!" {subj.said} {subj.pronoun} {subj.adverb}',
297 '"We must find out where that villian has taken {obj.pronoun}!" {subj.said} {subj.pronoun} {subj.adverb}',
298 )
299
300
301 class LocationHunchEvent(Event):
302 templates = (
303 '"I have a strong feeling {obj.pronoun} is in {object2.pronoun}," {subj.said} {subj.pronoun} {subj.adverb}',
304 )
305
306
307 class SetCourseEvent(Event):
308 templates = (
309 '"Set course for {object.pronoun}!," {subj.shouted} {subj.pronoun} {subj.adverb}',
310 )
311
312
313 class WeMustGoToEvent(Event):
314 templates = (
315 '"Quickly! We must make our way to {object.pronoun}!," {subj.shouted} {subj.pronoun} {subj.adverb}',
316 )
317
318
319 class RescueEvent(Event):
320 templates = (
321 '{subj.pronoun} together broke the cage and pulled out {obj.def}',
322 )
323
324
325 class BumpIntoForceFieldEvent(Event):
326 templates = (
327 'walking along, {subj.pronoun} <suddenly|unexpectedly|surprisedly> <smacked|whacked|bonked> {subj.his} head against an invisible force field',
328 )
329
330
331 class MustRetraceStepsEvent(Event):
332 templates = (
333 '"There`s no way we can get through this we`ll have to find another way in!" {subj.said} {subj.pronoun} {subj.adverb}',
334 )
335
336
337 # - - - - events for lost item plot - - - -
338
339
340 class CommentOnItemEvent(Event):
341 # NOTUSED
342 templates = (
343 '"<This is|What> a <great|superb> {obj.name} this is, <isn`t it|don`t you think|don`t you agree>?" {subj.pronoun} {subj.said} {subj.adverb}',
344 )
345
346
347 class TripEvent(Event):
348 templates = (
349 "<Just then|Suddenly|All of a sudden|Without warning>, {subj.pronoun} <tripped|stumbled|lost {subj.his} balance|stubbed {subj.his} toe|was distracted>",
350 )
351
352
353 class DropEvent(Event):
354 templates = (
355 "{subj.pronoun} lost {subj.his} grip on {obj.pronoun}",
356 "{obj.pronoun} slipped out of {subj.possessive} <grasp|hand|grip>",
357 )
358
359
360 class LoseEvent(Event):
361 templates = (
362 "{subj.pronoun} tumbled and rolled away out of sight",
363 "a <drone|Space Magpie> flew by and made off with {subj.pronoun}",
364 )
365
366
367 class OopsEvent(Event):
368 templates = (
369 '"<Whoops|Oops|Oopsie-daisy|Whoopsy-daisy|Uh-oh|Drat>," {subj.said} {subj.pronoun} quietly',
370 'a <sheepish|embarrassed|crestfallen|sour> look crept across {subj.possessive} face',
371 )
372
373
374 class HunchEvent(Event):
375 templates = (
376 '{subj.pronoun} <suddenly|all of a sudden> had a <funny|strange|unusual|odd|> <hunch|inkling|intuition>',
377 )
378
379
380 class LookBehindEvent(Event):
381 templates = (
382 '{subj.pronoun} looked behind the nearby {obj.name}',
383 )
384
385
386 class FindEvent(Event):
387 templates = (
388 '{subj.pronoun} found {obj.pronoun}',
389 )
390
391
392 # - - - - events for fight plot
393
394
395 class AttackEvent(Event):
396 templates = (
397 'suddenly, {subj.indef} attacked {obj.pronoun}',
398 'suddenly, {obj.pronoun} {obj.was} attacked by {subj.indef}',
399 )
400
401
402 class EncounterEvent(Event):
403 templates = (
404 'suddenly, {obj.pronoun} spotted {subj.indef} in the distance',
405 'suddenly, {subj.indef} came around the corner',
406 )
407
408
409 class GoonBanterEvent(Event):
410 templates = (
411 '"{obj.name}!" <hissed|mouthed|squeaked> {subj.def}',
412 '"<Uh oh, |>looks like <we`ve got company|they`ve spotted us|they`ve found us>," {subj.said} {subj.def} {subj.adverb}',
413 )
414
415
416 class GoonParlayEvent(Event):
417 templates = (
418 '"{subj.gibberish}!!!" shouted {subj.def} in {subj.his} <weird|strange> {subj.singular} language',
419 )
420
421
422 class AfterBattleEvent(Event):
423 templates = (
424 'when the dust had <cleared|settled>, <stunned|dazed> {subj.name} <littered|were strewn across> the <battlefield|area>',
425 )
426
427
428 class AfterBattleBanterEvent(Event):
429 templates = (
430 '"That was a <close one|close call>!" {subj.said} {subj.pronoun} {subj.adverb}',
431 )
432
433
434 class ObserveAfterBattleEvent(Event):
435 templates = (
436 '"It looked like {obj.distal} had {object2.possessive} insignia on their <uniforms|jerseys|clothes|armor>," {subj.said} {subj.pronoun} {subj.adverb}',
437 )
438
439
440 class RemindReportEvent(Event):
441 templates = (
442 '"Remember to include that in the report when we get home."',
443 )
444
445
446 class IfWeGetHomeEvent(Event):
447 templates = (
448 '"IF we get home," {subj.said} {subj.pronoun} {subj.adverb}',
449 )
450
451
452 class WarCryEvent(Event):
453 templates = (
454 '"{subj.war_cry}!!!" {subj.shouted} {subj.pronoun}',
455 )
456
457
458 class UnsheathEvent(Event):
459 templates = (
460 '{subj.pronoun} drew {obj.pronoun} out of {subj.his} <belt|equipment bag|backpack>',
461 )
462
463
464 class LiftWeaponEvent(Event):
465 templates = (
466 '{subj.pronoun} <lifted|heaved|raised> {obj.pronoun} <above|over> {subj.her} head',
467 )
468
469
470 class BringDownWeaponEvent(Event):
471 templates = (
472 '{subj.pronoun} brought down {obj.pronoun} with a <mighty|tremendous|awesome> <force|movement|power>',
473 )
474
475
476 class WeaponContactEvent(Event):
477 templates = (
478 '{subj.pronoun} made contact with {obj.pronoun} with a <mighty|tremendous|awesome> <thud|whack|impact>',
479 )
480
481
482 class FlyAcrossEvent(Event):
483 templates = (
484 '{subj.pronoun} went flying across the room',
485 )
486
487
488 class TryToGetBehindEvent(Event):
489 templates = (
490 '{subj.pronoun} <ran|sprinted> to the side, <looking to|to try to|trying to> attack {obj.pronoun} from <behind|the rear>',
491 )
492
493
494 class RushIntoFrayEvent(Event):
495 templates = (
496 'unperturbed, {subj.pronoun} rushed back into the <fray|melee|fight>',
497 )
498
499
500 # - - - - romantic tension events - - - -
501
502
503 class PullAsideEvent(Event):
504 templates = (
505 '{subj.motion}{subj.pronoun} <motioned|gestured to> {obj.accusative} to step <away|aside|back> from the others for a moment',
506 )
507
508
509 class WantToTalkToYouEvent(Event):
510 templates = (
511 '"There`s <something|a thing> I <wanted|need> to <talk to you about|talk about|say to you>", {subj.pronoun} <began|{subj.said}> {subj.adverb}',
512 )
513
514
515 class WhatIsItEvent(Event):
516 templates = (
517 '"<Yes|What is it>, {obj.def}?" asked {subj.pronoun}',
518 )
519
520
521 class RecallPastEventEvent(Event):
522 templates = (
523 '"<Uh|Um|Well|You know>, about <that time|last night|the other day> in the <mess hall|engine room|loading bay>..." {subj.said} {subj.pronoun} {subj.adverb}',
524 )
525
526
527 class SayNoMoreEvent(Event):
528 templates = (
529 '"Hush, {obj.def}, there`s no need, you know that," {subj.said} {subj.pronoun} {subj.adverb}',
530 '"It`s all right, {obj.def}, you don`t need to say anything," {subj.said} {subj.pronoun} {subj.adverb}',
531 '"Speak not of it, {obj.def}" {subj.said} {subj.pronoun} {subj.adverb}',
532 '{subj.pronoun} held {subj.her} finger up to {obj.possessive} lips',
533 '"Words are not necessary, {obj.def}" {subj.said} {subj.pronoun} {subj.adverb}',
534 )
535
536
537 class BumpIntoAwkwardlyEvent(Event):
538 templates = (
539 '{subj.pronoun} wasn`t <looking|watching> where {sub.pronoun} was going and bumped into {obj.pronoun} in a most embarrassing fashion',
540 )
541
542
543 class BlushEvent(Event):
544 templates = (
545 '{subj.pronoun} blushed a deep red',
546 '{subj.pronoun} went beet red with blushing',
547 )
548
549
550 class PreludeToKissEvent(Event):
551 templates = (
552 '"<Uh|Um|Well|You know>, ... there comes a time when..." {subj.said} {subj.pronoun} {subj.adverb}',
553 )
554
555
556 class FidgetEvent(Event):
557 templates = (
558 '"<Uh|Um|Well|You know>, ..." {subj.said} {subj.pronoun} {subj.adverb}',
559 '{subj.pronoun} put {subj.his} hand behind {subj.his} head',
560 '{subj.pronoun} scratched {subj.his} <head|knee|neck>',
561 '{subj.pronoun} looked away',
562 )
563
564 class FacesCloseTogetherEvent(Event):
565 new_para = True
566 templates = (
567 'The faces of {subj.definite} and {obj.definite} moved centimeters closer',
568 'The faces of {obj.definite} and {subj.definite} moved centimeters closer',
569 'Their faces inched closer and closer',
570 )
571
572
573 class MushyStuffEvent(Event):
574 templates = (
575 '"{obj.def}, you are so exquisitely beautiful, like a jewel," {sub.said} {subj.pronoun}. "I must kiss you!"',
576 )
577
578
579 class KissEvent(Event):
580 exciting = True
581 new_para = True
582 templates = (
583 '{subj.def} and {obj.def} kissed',
584 )
585
586
587 class AndTheyKissedEvent(Event):
588 exciting = True
589 templates = (
590 ('And they kissed' + (' and they kissed' * 20)),
591 )
592
593
594 # - - - - events for drone "plot" - - - -
595
596
597 class DroneEvent(Event):
598 templates = (
599 '<Suddenly|From out of nowhere|Out of the blue>, a <courier|messenger|delivery|security|surveillance|cooking|cleaning> drone <whizzed past|buzzed by|flew close by> {subj.possessive} head',
600 )
601
602
603 class WhatWasThatEvent(Event):
604 templates = (
605 '"What <in blazes|in Sam Hill|in the world|the devil|> was that?" {subj.said} {subj.pronoun} {subj.adverb}',
606 )
607
608
609 # - - - - generic plot-related events - - - -
610
611
612 class KeepMovingEvent(Event):
613 templates = (
614 '"Let`s keep moving," {subj.pronoun} {subj.said} {subj.adverb}',
615 )
616
617
618 class TurnCornerEvent(Event):
619 templates = (
620 '{subj.pronoun} <went around|turned> a corner',
621 )
622
623
624 class PanicEvent(Event):
625 templates = (
626 '{subj.pronoun} {subj.shouted} "<OH NO|NOOO|Nooooooooooooo>!"',
627 '"<OH NO|NOOO|Nooooooooooooo>!" {subj.shouted} {subj.pronoun} <{subj.adverb}|{subj.withvoice}>',
628 '{subj.pronoun} <put|clapped> {subj.her} hands '
629 '<on|to> <the sides of {subj.her} face|{subj.her} cheeks> in <disbelief|surprise|shock>',
630 '{subj.possessive} mouth <opened|gaped|gawped> wide in <disbelief|surprise|shock>',
631 '{subj.pronoun} made <motions|gestures> of <disbelief|surprise|shock> with {subj.her} <arms|hands>',
632 )
633
634
635 class WasteNoTimeEvent(Event):
636 templates = (
637 '{subj.pronoun} wasted no time',
638 '"There`s no time to lose!" {subj.pronoun} {subj.shouted}',
639 '"We must <act quickly|hurry>!" {subj.pronoun} {subj.said}',
640 '"Hurry!" {subj.pronoun} {subj.said}',
641 )
642
643
644 class ThankEvent(Event):
645 templates = (
646 '"Thank you, {obj.def}," {subj.said} {subj.pronoun} {subj.adverb}',
647 '"I would have been <lost|a goner|toast> without you, {obj.def}," {subj.said} {subj.pronoun} {subj.adverb}',
648 '"I am <indebted|in debt> to you, {obj.def}," {subj.said} {subj.pronoun} {subj.adverb}',
649 )
650
651
652 class ReliefEvent(Event):
653 templates = (
654 '"<What|That`s> a relief!" {subj.shouted} {subj.pronoun}',
655 )
656
657
658 # - - - - end-of-story events
659
660
661 class ComplimentActionEvent(Event):
662 templates = (
663 '"<That|there> was some <quick|top notch|ace> <thinking|action> <I saw|> from you today, {obj.def}!" {subj.said} {subj.pronoun} {subj.adverb}',
664 '"<You displayed|I saw you display> some <quick|top notch|ace> <thinking|action> today, {obj.def}!" {subj.said} {subj.pronoun} {subj.adverb}',
665 )
666
667
668 class OfferPromotionEvent(Event):
669 templates = (
670 '"I think I should promote you to {obj.next_rank}, {obj.def}!" {subj.said} {subj.pronoun} {subj.adverb}',
671 '"How would you like a promotion to the rank of {obj.next_rank}, {obj.def}!" {subj.said} {subj.pronoun} {subj.adverb}',
672 )
673
674
675 class OhYouEvent(Event):
676 templates = (
677 '"Oh, {obj.def}!" <{subj.said}|blushed> {subj.pronoun} {subj.adverb}',
678 '"Oh, {obj.def}!" {subj.said} {subj.pronoun}, blushing fiercely like <a tiger|a bonfire>',
679 )
680
681
682 class RefusePromotionEvent(Event):
683 templates = (
684 '"I`m too modest, I like being a {subj.rank} too!"',
685 '"I`m not in this for rank, I do it out of love!"',
686 '"it`s enough to know I`m fighting on the side of good!"',
687 )
688
689
690 class ConvalesceEvent(Event):
691 templates = (
692 '"What an adventure that was!" exclaimed {subj.pronoun}',
693 '"I could really go for a <curry|pizza|hamburger|hot dog|soda|parmo> <after that|right now>," {subj.said} {subj.pronoun} {subj.adverb}',
694 '"I think we all learned a valuable lesson today," {subj.said} {subj.pronoun} {subj.adverb}',
695 '"All in a day`s work for the Space Fighters!" {subj.said} {subj.pronoun} {subj.adverb}',
696 '{subj.motion}{subj.pronoun} <slumped|flumped> down on the <couch|sofa|bean bag chair>',
697 )
698
699
700 class RememberMacGuffinEvent(Event):
701 templates = (
702 '"Maybe next time you`ll be more careful with {object2.distal}, {obj.def}," {subj.said} {subj.pronoun} {subj.adverb}',
703 )
704
705
706 class AllLaughEvent(Event):
707 templates = (
708 "they all laughed",
709 "everyone laughed",
710 )
711
712
713 # - - - - travel events
714
715
716 class TravelToEvent(Event):
717 templates = (
718 '{subj.pronoun} travelled to {obj.pronoun}',
719 )
720
721
722 class CommentOnPlaceEvent(Event):
723 templates = (
724 '"I don`t like the look of this place," {subj.said} {subj.pronoun} {subj.adverb}',
725 '"This place gives me the <creeps|chills|willies>," {subj.said} {subj.pronoun} {subj.adverb}',
726 )
727
728
729 class TakeReadingsEvent(Event):
730 templates = (
731 '"Enviro <readings|measurements|metrics> look normal," {subj.said} {subj.pronoun}, <looking|peering|gazing> down at <the enviro probe app on {subj.his} hyper crystal|{subj.his} hyper crystal`s enviro probe app>',
732 '{subj.motion}{subj.pronoun} <dialed up|opened up|started|launched> the enviro probe app on {subj.his} hyper crystal. "Enviro <readings|measurements|metrics> look normal," {subj.said} {subj.pronoun} {subj.adverb}',
733 )
734
735
736 class ReadingsBanterEvent(Event):
737 templates = (
738 '"<Yeah, normal|Sure, normal|Normal, yeah|Normal, sure|>... for a <PIT|DUMP|DISASTER AREA|TOXIC WASTE SITE>!!" {subj.said} {subj.pronoun} {subj.adverb}',
739 )
740
741
742 class BlastOfHotAirEvent(Event):
743 templates = (
744 '{subj.pronoun} close {subj.his} eyes as a <blast|gust> of warm fetid air rushed through the passage',
745 )
746
747
748 # - - - - misc
749
750
751 class OutOfEggsEvent(Event):
752 templates = (
753 '"I have just come back from the <kitchen|pantry> and it looks like we are <fresh|plum> out of <eggs|Space Sugar|nutri worms>," {subj.said} {subj.pronoun} {subj.adverb}',
754 )
755
756
757 class GenericHyperCrystalUsageEvent(Event):
758 templates = (
759 '{subj.pronoun} was playing that new match three game on {subj.her} hyper crystal',
760 )
761
762
763 # - - - - descriptions, not events - - - -
764
765
766 class PoseDescription(Event):
767 templates = (
768 '{subj.pronoun} {subj.was} leaning on {obj.pronoun}',
769 '{subj.pronoun} {subj.was} standing near {obj.pronoun}',
770 '{subj.pronoun} {subj.was} standing near {obj.pronoun}',
771 )
772
773
774 class SettingDescription(Event):
775 templates = (
776 'all was quiet {subj.preposition} {subj.def}',
777 '{subj.def} was a sight to behold',
778 "{subj.def} was pretty much what you'd expect",
779 )
780
781
782 class NearbyDescription(Event):
783 # subject is the setting, so it joins up with SettingDescription
784 templates = (
785 '<Nearby|Not too far away|In the centre of the area> {obj.was} {obj.indef}',
786 'there {obj.was} {obj.indef} off in the <corner|distance>',
787 '{obj.indef} seemed to dominate the environment',
788 '{obj.indef} gleamed in the {obj.location.light}',
789 )
790
791
792 class GenericSettingDescription(Event):
793 # subject is the setting, so it joins up with SettingDescription
794 templates = (
795 'a Space Mouse scurried by',
796 'a few Space <Bees|Gnats|Mosquitoes> <flew|buzzed> around',
797 'there seemed to be electricity in the air',
798 'it was truly an unusual sight',
799 )
800
801
802 class CharacterDescription(Event):
803 is_conjoinable = False
804 templates = (
805 '{subj.pronoun} was {subj.stature}, with {subj.hair_length} {subj.hair_colour} hair and {subj.eye_colour} eyes',
806 )
807
808
809 class CharacterFeaturesDescription(Event):
810 templates = (
811 '{subj.pronoun} had a {subj.feature_adj} {subj.feature}, the <sort|kind|type> you only see in old movies',
812 '{subj.pronoun} had a {subj.feature_adj} {subj.feature}, the <sort|kind|type> you only see in Westerns',
813 '{subj.pronoun} had a {subj.feature_adj} {subj.feature}, the <sort|kind|type> you only see in comic books',
814 '{subj.pronoun} had a {subj.feature_adj} {subj.feature}, the <sort|kind|type> you only see in the Outer Colonies',
815 '{subj.pronoun} had a {subj.feature_adj} {subj.feature} that {subj.she} <inherited|inherited from|got> from {subj.her} <mother|father><`s side|`s side of the family|>',
816 '{subj.pronoun} had a {subj.feature_adj} {subj.feature} that the other kids <teased|made fun of> back when {subj.he} <was in school|was a kid>',
817 '{subj.her} {subj.feature_adj} {subj.feature} complemented {subj.her} other features <well|strongly|nicely|perfectly|highly>',
818 )
819
820
821 class CharacterReminder(Event):
822 templates = (
823 '{subj.pronoun} put {subj.his} hand up to {subj.his} {subj.feature_adj} {subj.feature}',
824 '{subj.his} {subj.feature_adj} {subj.feature} seemed to <gleam|glow|shine> in the {subj.location.light}',
825 '{subj.his} {subj.hair_length} {subj.hair_colour} hair seemed to <gleam|glow|shine> in the {subj.location.light}',
826 'the {subj.location.light} of {subj.location.definite} cast highlights on {subj.his} {subj.hair_length} {subj.hair_colour} <hair|locks>',
827 '{subj.his} {subj.feature_adj} {subj.feature} in profile cast a shadow against {subj.location.nearby_scenery.definite}',
828 '{subj.his} {subj.feature_adj} {subj.feature} in profile threw a shadow on {subj.location.nearby_scenery.definite}',
829 )
830
831
832 class TorsoCostumeDescription(Event):
833 two_piece_templates = (
834 '{subj.pronoun} {subj.was} {subj.wearing} {subj.torso_costume.indef} and {subj.legs_costume.indef}',
835 )
836
837 one_piece_templates = (
838 '{subj.pronoun} {subj.was} {subj.wearing} {subj.torso_costume.indef}',
839 )
840
841 def render(self):
842 if self.subject.legs_costume:
843 template = random.choice(self.two_piece_templates)
844 else:
845 template = random.choice(self.one_piece_templates)
846 return self.render_t(template)
847
848
849 class FeetCostumeDescription(Event):
850 templates = (
851 'on {subj.her} feet {subj.pronoun} wore {subj.feet_costume.indef}',
852 '{subj.feet_costume.indef} <graced|were on> {subj.her} feet',
853 '{subj.his} feet were shod <with|in> {subj.feet_costume.indef}',
854 '{subj.feet_costume.def} that <were on {subj.his} feet|{subj.pronoun} had on> looked almost new',
855 )
856
857
858 class TorsoCostumeReminder(Event):
859 two_piece_templates = (
860 '{subj.torso_costume.definite} {subj.pronoun} {subj.was} {subj.wearing} seemed to <gleam|glow|shine> in the {subj.location.light}',
861 )
862
863 one_piece_templates = (
864 '{subj.torso_costume.definite} {subj.pronoun} {subj.was} {subj.wearing} seemed to <gleam|glow|shine> in the {subj.location.light}',
865 )
866
867 def render(self):
868 if self.subject.legs_costume:
869 template = random.choice(self.two_piece_templates)
870 else:
871 template = random.choice(self.one_piece_templates)
872 return self.render_t(template)
873
874
875 class FeetCostumeReminder(Event):
876 templates = (
877 '{subj.feet_costume.definite} {subj.pronoun} {subj.was} {subj.wearing} seemed to <gleam|glow|shine> in the {subj.location.light}',
878 '{subj.feet_costume.definite} on {subj.her} feet seemed to <gleam|glow|shine> in the {subj.location.light}',
879 )
880
881
882 class BehindBarsDescription(Event):
883 exciting = True
884 new_para = True
885 templates = (
886 '{subj.pronoun} was there, locked in a cage',
887 )
888
889
890 class GenericBattleDescription(Event):
891 exciting = True
892 new_para = True
893 templates = (
894 'a <fierce|intense|harrowing|epic> <battle|skirmish> <ensued|took place|began|had begun|commenced|started>',
895 )
896
897
898 # - - - -
899
900
901 class ConjoinedEvent(Event):
902 slots = (
903 'subject', 'object', 'object2', # MEH
904 'event1', 'event2',
905 )
906
907 def render(self):
908 return self.event1.render() + ", and " + self.event2.render()
909
910
911 # - - - -
912
913
914 def get_event_class(name):
915 cls = globals().get(name)
916 return cls
917
918
919 def get_all_event_classes():
920 return [c for c in globals().values() if c.__class__ == type and issubclass(c, Event)]
0 """Objects in the story.
1
2 This includes characters, which are merely animate objects. And settings.
3
4 The root object of all objects is Object.
5
6 Objects here contain only the properties of the object which do not change during the
7 course of the story. See marysue.state.State for states of objects over time.
8
9 """
10
11 import string
12
13 import marysue.util as random
14
15
16 class Object(object):
17 """Objects are immutable."""
18
19 def __init__(self, names, takeable=False, home=None, rank=None, weapon=None):
20 self.names = tuple(names)
21 self.takeable = takeable
22 self.home = home
23 self.rank = rank
24 self.weapon = weapon
25
26 def __hash__(self):
27 return hash(self.names)
28 # ...and the other stuff too, I guess, but in practice no.
29
30 def __eq__(self, other):
31 return isinstance(other, Object) and self.names == other.names
32 # ...and the other stuff too, I guess, but in practice no.
33
34 def __repr__(self):
35 return "%s(names=%r)" % (self.__class__.__name__, self.names)
36 # ...and the other stuff too, I guess, but in practice no.
37
38 @property
39 def name(self):
40 return self.names[0].replace('{rank}', str(getattr(self, 'rank', '')))
41
42 @property
43 def indefinite_article(self):
44 return 'a '
45
46 @property
47 def definite_article(self):
48 return 'the '
49
50 # Not sure how I feel about these three methods -- see marysue.state.State
51
52 @property
53 def definite(self):
54 article = self.definite_article
55 fnite = self._fnite()
56 return article + fnite
57
58 @property
59 def indefinite(self):
60 article = self.indefinite_article
61 fnite = self._fnite()
62 if article == 'a 'and fnite[0].upper() in ('A', 'E', 'I', 'O', 'U'):
63 article = 'an '
64 return article + fnite
65
66 def _fnite(self):
67 return self.name
68
69 @property
70 def possessive(self):
71 return self.definite + "'s"
72
73 @property
74 def possessive_pronoun(self):
75 return 'its'
76
77 @property
78 def accusative_pronoun(self):
79 return "it"
80
81 @property
82 def accusative(self):
83 return self.accusative_pronoun
84
85 @property
86 def pronoun(self):
87 return "it"
88
89 @property
90 def distal_pronoun(self):
91 return 'that'
92
93 @property
94 def proximal_pronoun(self):
95 return 'this'
96
97 @property
98 def distal(self):
99 return self.distal_pronoun + " " + self._fnite()
100
101 @property
102 def proximal(self):
103 return self.proximal_pronoun + " " + self._fnite()
104
105 @property
106 def was(self):
107 return "was"
108
109 @property
110 def colours(self):
111 return (
112 'red', 'yellow', 'blue', 'purple', 'green', 'orange',
113 'mauve', 'brown', 'black', 'white',
114 'tan', 'chartreuse', 'maroon', 'pink', 'violet',
115 'orange red', 'blue green', 'navy blue',
116 'gold coloured', 'silver coloured',
117 #'fuschia', 'ecru', 'puce',
118
119 'floral print', 'plaid', 'tie dyed', 'pinstripe',
120 # 'herringbone',
121 # 'houndstooth',
122 )
123
124 @property
125 def colour(self):
126 return random.choice(self.colours)
127
128 @property
129 def gibberish(self):
130 def w():
131 return ''.join(random.lowercase() for _ in xrange(0, random.randint(3, 8)))
132 words = [w() for _ in xrange(0, random.randint(3, 9))]
133 return ' '.join(words)
134
135
136 class PluralMixin(object):
137 @property
138 def indefinite_article(self):
139 return 'some '
140
141 @property
142 def definite_article(self):
143 return 'the '
144
145 @property
146 def possessive_pronoun(self):
147 return "their"
148
149 @property
150 def accusative_pronoun(self):
151 return "them"
152
153 @property
154 def pronoun(self):
155 return "them"
156
157 @property
158 def distal_pronoun(self):
159 return 'those'
160
161 @property
162 def proximal_pronoun(self):
163 return 'these'
164
165 @property
166 def was(self):
167 return "were"
168
169 @property
170 def singular(self):
171 if self.name.endswith('s'):
172 return self.name[:-1]
173 else:
174 return self.name
175
176
177 class Plural(PluralMixin, Object):
178 pass
179
180
181 class ProperMixin(object):
182 @property
183 def indefinite_article(self):
184 return ''
185
186 @property
187 def definite_article(self):
188 return ''
189
190
191 class Proper(ProperMixin, Object):
192 pass
193
194
195 class MasculineMixin(ProperMixin):
196 @property
197 def possessive_pronoun(self):
198 return "his"
199
200 @property
201 def accusative_pronoun(self):
202 return "him"
203
204 @property
205 def pronoun(self):
206 return "he"
207
208
209 class FeminineMixin(Object):
210 @property
211 def possessive_pronoun(self):
212 return "her"
213
214 @property
215 def accusative_pronoun(self):
216 return "her"
217
218 @property
219 def pronoun(self):
220 return "she"
221
222
223 class Group(PluralMixin):
224 def __init__(self, *subjects):
225 if len(subjects) == 0:
226 raise ValueError("Group must include at least one object")
227 self.subjects = subjects
228
229 def __repr__(self):
230 children = ', '.join([repr(c) for c in self.subjects])
231 return "%s(%s)" % (self.__class__.__name__, children)
232
233 def __iter__(self):
234 return self.subjects.__iter__()
235
236 def __getitem__(self, index):
237 return self.subjects[index]
238
239 def __len__(self):
240 return len(self.subjects)
241
242 def __add__(self, other):
243 total = self.subjects + tuple(o for o in other)
244 return Group(*total)
245
246 @property
247 def definite(self):
248 if len(self.subjects) == 0:
249 return 'no one'
250 elif len(self.subjects) == 1:
251 return self.subjects[0].definite
252 elif len(self.subjects) == 2:
253 return self.subjects[0].definite + ' and ' + self.subjects[1].definite
254 return ', '.join([s.definite for s in self.subjects[:-1]]) + ', and ' + self.subjects[-1].definite
255
256 @property
257 def pronoun(self):
258 return self.definite
0 import marysue.util as random
1 from marysue.ast import AST
2 from marysue.storytree import EventSequence
3 from marysue.events import *
4 from marysue.duties import RescueDuty, RetrieveDuty
5 from marysue.characters import Character, TheOptimist, MarySue
6
7
8 class Plot(AST):
9 slots = (
10 'subject', # often a Group instead of a single character
11 'object', # usually a single characters
12 'object2', # used rarely
13 'bystanders', # other characters witnessing this
14 'exeunt', # characters eligible to travel to another setting
15 'disqualified',
16 'requalified',
17 'setting', # the current setting
18 )
19
20
21 class PlotSequence(Plot):
22 """Acts as a container for (sub)sequences of PlotDevelopments."""
23
24 def render(self):
25 return '. '.join([child.render() for child in self]) + '.'
26
27 def print_synopsis(self):
28 for n, point in enumerate(self):
29 assert point.setting, point
30 print "%s. %s %s,\n %s." % (
31 n, point.setting.preposition, point.setting.definite,
32 point.render()
33 )
34
35 def flatten(self):
36 return self.__class__(*list(self.all_children()), **dict(self.iteritems()))
37
38 def all_children(self):
39 for child in self:
40 if isinstance(child, self.__class__):
41 for descendant in child.all_children():
42 yield descendant
43 else:
44 yield child
45
46
47 class PlotDevelopment(Plot):
48 def create_events(self):
49 raise NotImplementedError
50
51 def all_involved_characters(self):
52 return Character.characters_to_set(self.subject, self.object, self.object2, self.bystanders)
53
54
55
56 # - - - -
57
58
59 class Introduction(PlotDevelopment):
60 templates = (
61 "We are introduced to {subject.def}",
62 )
63
64 def create_events(self):
65 e = [BecomeHappyEvent(subject=s) for s in self.subject]
66 seen = set()
67 for s in self.subject:
68 if not seen or random.chance(66):
69 if random.chance(85):
70 e.append(PoseDescription(subject=s))
71 else:
72 e.append(random.choice((
73 OutOfEggsEvent(subject=s),
74 GenericHyperCrystalUsageEvent(subject=s),
75 )))
76 if seen and random.chance(66):
77 e.append(GreetEvent(subject=s, object=random.choice(seen)))
78 seen.add(s)
79 return e
80
81
82 class ChekovsGun(PlotDevelopment):
83 templates = (
84 "{object.def} is foreshadowed by {subject.def}, witnessed by {bystanders.def}",
85 )
86
87 def create_events(self):
88 holder = self.subject
89 asker = random.choice(self.bystanders)
90 return [
91 EventSequence(
92 IntroduceItemEvent(subject=holder, object=self.object),
93 AskAboutItemEvent(subject=asker, object=self.object),
94 ReplyAboutItemEvent(subject=holder, object=asker),
95 )
96 ]
97
98
99 class ChekovsFun(PlotDevelopment):
100 templates = (
101 "{subject.def} is reminded of their mishaps with {object.def}, witnessed by {bystanders.def}",
102 )
103
104 def create_events(self):
105 holder = self.subject
106 asker = random.choice(self.bystanders)
107 return [
108 EventSequence(
109 RememberMacGuffinEvent(subject=asker, object=holder, object2=self.object),
110 )
111 ]
112
113
114 class Convalescence(PlotDevelopment):
115 templates = (
116 "{subject.def} convalesce after their adventures",
117 )
118
119 def create_events(self):
120 prots = set(self.subject)
121
122 ms = random.extract(prots, filter=lambda x: isinstance(x, MarySue))
123 capt = random.extract(prots, filter=lambda x: isinstance(x, TheOptimist))
124
125 assert ms and capt
126 e = [
127 ComplimentActionEvent(subject=capt, object=ms),
128 OfferPromotionEvent(subject=capt, object=ms),
129 OhYouEvent(subject=ms, object=capt),
130 RefusePromotionEvent(subject=ms, object=capt),
131 ]
132 e += [
133 EventSequence(
134 BecomeHappyEvent(subject=s),
135 ConvalesceEvent(subject=s),
136 ) for s in self.subject
137 ]
138 return e
139
140
141 class AllLaugh(PlotDevelopment):
142 # It may seem odd for this to be a plot development, but it
143 # turns out to be the easiest way to do it in the current architecture
144 templates = (
145 "{subject.def} all laugh",
146 )
147
148 def create_events(self):
149 return [
150 AllLaughEvent(),
151 ]
152
153
154 # - - - -
155
156
157 class Kidnapping(PlotDevelopment):
158 templates = (
159 "{subject.def} kidnaps {obj.def}, witnessed by {bystanders.def}",
160 )
161
162 def create_events(self):
163 all_protagonists = list(self.bystanders) + [self.object]
164 perp = self.subject
165 duty = RescueDuty(self.object)
166 return [
167 BecomeHappyEvent(subject=perp),
168 AppearEvent(subject=perp),
169 ] + [
170 BecomeAngryEvent(subject=s) for s in all_protagonists
171 ] + [
172 NoticeAntagonistEvent(subject=random.choice(self.bystanders), object=perp),
173 CackleEvent(subject=perp),
174 AntagonistBanterEvent(subject=perp),
175 AbductEvent(subject=perp, object=self.object),
176 DisappearEvent(subject=perp, object=self.object),
177 ] + [
178 EventSequence(
179 BecomeAngryEvent(subject=s),
180 PanicEvent(subject=s),
181 AcquireDutyEvent(subject=s, object=duty),
182 ) for s in self.bystanders
183 ]
184
185
186 class LocateAbductee(PlotDevelopment):
187 templates = (
188 "{subject.def} must locate where {object.def} is being held",
189 )
190
191 def create_events(self):
192 prots = set(self.subject)
193 b = random.extract(prots, filter=lambda x: isinstance(x, MarySue))
194 if not b:
195 b = random.extract(prots)
196 a = random.extract(prots)
197 if not a:
198 a = b
199 e = [
200 WeMustFindThemEvent(subject=a, object=self.object),
201 LocationHunchEvent(subject=b, object=self.object, object2=self.object2),
202 ]
203 if self.setting == a.home:
204 e.append(SetCourseEvent(subject=a, object=self.object2))
205 else:
206 e.append(WeMustGoToEvent(subject=a, object=self.object2))
207 return e
208
209
210 class WayBlocked(PlotDevelopment):
211 templates = (
212 "{subject.def} finds {subject.his} way is blocked",
213 )
214
215 def create_events(self):
216 prots = set(self.subject)
217 a = random.extract(prots)
218 b = random.extract(prots)
219 return [
220 BumpIntoForceFieldEvent(subject=a),
221 BecomeSadEvent(subject=a),
222 EventSequence(
223 WhatWasThatEvent(subject=b),
224 BumpIntoForceFieldEvent(subject=b),
225 BecomeSadEvent(subject=b),
226 ) if b else None,
227 MustRetraceStepsEvent(subject=a),
228 StateDutyEvent(subject=a),
229 ]
230
231
232 class Rescue(PlotDevelopment):
233 templates = (
234 "{subject.def} rescues {obj.def}",
235 )
236
237 def create_events(self):
238 # Note: even though this is a different Duty object than was
239 # put in the character's duties list, apparently it will work
240 # because we made these things immutable -- they hash the same.
241 duty = RescueDuty(self.object)
242 return [
243 KeepMovingEvent(subject=random.choice(self.subject)),
244 #WhatWasThatNoiseEvent(subject=random.choice(self.subject)),
245 TurnCornerEvent(subject=self.subject),
246 BehindBarsDescription(subject=self.object),
247 ] + [
248 RescueEvent(subject=self.subject, object=self.object),
249 BecomeHappyEvent(subject=self.object),
250 ] + [
251 RelieveDutyEvent(subject=s, object=duty) for s in self.subject
252 ] + [
253 EventSequence(
254 ThankEvent(subject=self.object, object=s),
255 BecomeHappyEvent(subject=s),
256 ) for s in self.subject
257 ]
258
259
260 # - - - -
261
262
263 class LoseItem(PlotDevelopment):
264 templates = (
265 "{subject.def} loses {obj.def}, witnessed by {bystanders.def}",
266 )
267
268 def create_events(self):
269 s = self.subject
270 duty = RetrieveDuty(self.object)
271 e = [
272 LookAroundEvent(subject=s),
273 HoldEvent(subject=s, object=self.object),
274 #CommentOnItemEvent(subject=s, object=self.object),
275 TripEvent(subject=s),
276 DropEvent(subject=s, object=self.object),
277 LoseEvent(subject=self.object),
278 BecomeEmbarrassedEvent(subject=s),
279 OopsEvent(subject=s),
280 AcquireDutyEvent(subject=s, object=duty),
281 ]
282 for bystander in self.bystanders:
283 e.append(BecomeSadEvent(subject=bystander))
284 e.append(AcquireDutyEvent(subject=bystander, object=duty))
285 e.append(
286 random.choice((
287 LookAtEvent(subject=bystander, object=s),
288 GestureAtEvent(subject=bystander, object=s),
289 ))
290 )
291
292 return e
293
294
295 class RecoverItem(PlotDevelopment):
296 templates = (
297 "{subject.def} recovers {obj.def}, witnessed by {bystanders.def}",
298 )
299
300 def create_events(self):
301 s = self.subject
302 duty = RetrieveDuty(self.object)
303 return [
304 LookAroundEvent(subject=s),
305 HunchEvent(subject=s),
306 LookBehindEvent(subject=s, object=self.setting.nearby_scenery),
307 FindEvent(subject=s, object=self.object),
308 RelieveDutyEvent(subject=s, object=duty),
309 BecomeHappyEvent(subject=s),
310 ] + [
311 EventSequence(
312 RelieveDutyEvent(subject=bystander, object=duty),
313 BecomeHappyEvent(subject=bystander),
314 ) for bystander in self.bystanders
315 ] + [
316 ReliefEvent(subject=random.choice(self.bystanders))
317 ]
318
319
320 # - - - -
321
322
323 class TrappedInRubble(PlotDevelopment):
324 templates = (
325 "{subject.def} is trapped under rubble, witnessed by {bystanders.def}",
326 )
327
328 def create_events(self):
329 s = self.subject
330 duty = RescueDuty(self.subject)
331 return [
332 RumblingSoundEvent(),
333 WhatWasThatNoiseEvent(subject=random.choice(self.bystanders)),
334 CaveInEvent(subject=self.setting.roof),
335 StunnedEvent(subject=s),
336 BuriedUnderRubbleEvent(subject=s),
337 BecomeSadEvent(subject=s),
338 ] + [
339 EventSequence(
340 BecomeSadEvent(subject=bystander),
341 PanicEvent(subject=bystander),
342 AcquireDutyEvent(subject=bystander, object=duty),
343 ) for bystander in self.bystanders
344 ]
345
346
347 class ExtractedFromRubble(PlotDevelopment):
348 templates = (
349 "{subject.def} dig {obj.def} out of the rubble",
350 )
351
352 def create_events(self):
353 duty = RescueDuty(self.object)
354 return [
355 EventSequence(
356 WasteNoTimeEvent(subject=s),
357 DigOutEvent(subject=s, object=self.object)
358 ) for s in self.subject
359 ] + [
360 BecomeHappyEvent(subject=self.object),
361 ] + [
362 EventSequence(
363 ThankEvent(subject=self.object, object=s),
364 BecomeHappyEvent(subject=s),
365 RelieveDutyEvent(subject=s, object=duty),
366 ) for s in self.subject
367 ]
368
369
370 # - - - -
371
372
373 class GoonAmbush(PlotDevelopment):
374 templates = (
375 "{subject.def} are ambushed by {obj.def}",
376 )
377
378 def create_events(self):
379 return [
380 AttackEvent(subject=self.object, object=self.subject),
381 ] + [
382 EventSequence(
383 BecomeAngryEvent(subject=s),
384 ) for s in self.subject
385 ] + [
386 EventSequence(
387 GenericBattleDescription(),
388 )
389 ]
390
391
392 class GoonEncounter(PlotDevelopment):
393 templates = (
394 "{subject.def} encounter a band of {obj.def}",
395 )
396
397 def create_events(self):
398 prots = set(self.subject)
399 a = random.extract(prots)
400 return [
401 EncounterEvent(subject=self.object, object=self.subject),
402 GoonBanterEvent(subject=a, object=self.object),
403 GoonParlayEvent(subject=self.object),
404 ] + [
405 EventSequence(
406 BecomeAngryEvent(subject=s),
407 ) for s in self.subject
408 ] + [
409 EventSequence(
410 GenericBattleDescription(),
411 )
412 ]
413
414
415 class ProtagonistAttack(PlotDevelopment):
416 templates = (
417 "{subject.def} attack {obj.def}",
418 )
419
420 def create_events(self):
421 e = []
422 # maybe later a fight will have all subjects. FOR NOW,
423 s = random.choice(self.subject)
424 w = s.weapon
425 e.append(
426 EventSequence(
427 WarCryEvent(subject=s),
428 UnsheathEvent(subject=s, object=w),
429 LiftWeaponEvent(subject=s, object=w),
430 BringDownWeaponEvent(subject=s, object=w),
431 WeaponContactEvent(subject=w, object=self.object),
432 FlyAcrossEvent(subject=self.object),
433 )
434 )
435 return e
436
437
438 class AwkwardCombat(PlotDevelopment):
439 templates = (
440 "{subject.def} get in awkward situation during combat with {obj.def}",
441 )
442
443 def create_events(self):
444 mary_sue = self.subject[0]
445 dreamboat = self.subject[1]
446 return [
447 EventSequence(
448 TryToGetBehindEvent(subject=mary_sue, object=self.object),
449 BumpIntoAwkwardlyEvent(subject=mary_sue, object=dreamboat),
450 LookAtEvent(subject=dreamboat, object=mary_sue),
451 BlushEvent(subject=mary_sue),
452 BecomeEmbarrassedEvent(subject=mary_sue),
453 RushIntoFrayEvent(subject=dreamboat),
454 )
455 ]
456
457
458 class Vanquished(PlotDevelopment):
459 templates = (
460 "{subject.def} vanquish {obj.def} (they were sent by {object2.def})",
461 )
462
463 def create_events(self):
464 e = [
465 AfterBattleEvent(subject=self.object),
466 ]
467 s = random.choice(self.subject)
468 e.append(
469 EventSequence(
470 AfterBattleBanterEvent(subject=random.choice(self.subject)),
471 )
472 )
473 for s in self.subject:
474 e.append(BecomeHappyEvent(subject=s))
475
476 subjects = set(self.subject)
477 a = random.extract(subjects)
478 b = random.extract(subjects, filter=lambda x: not isinstance(x, TheOptimist))
479 e.append(
480 EventSequence(
481 BecomeAngryEvent(subject=self.object2), # b/c they need to have a mood assigned first!
482 ObserveAfterBattleEvent(subject=a, object=self.object, object2=self.object2),
483 RemindReportEvent(subject=a),
484 )
485 )
486 if b:
487 e.append(
488 EventSequence(
489 BecomeSadEvent(subject=b),
490 IfWeGetHomeEvent(subject=b),
491 )
492 )
493
494 return e
495
496
497 # - - - -
498
499
500 class AwkwardTension(PlotDevelopment):
501 templates = (
502 "There is an awkward moment between {subject.def} and {obj.def}",
503 )
504
505 def create_events(self):
506 s = self.subject
507 return [
508 BecomeEmbarrassedEvent(subject=self.subject),
509 PullAsideEvent(subject=self.subject, object=self.object),
510 WantToTalkToYouEvent(subject=self.subject, object=self.object),
511 WhatIsItEvent(subject=self.object, object=self.subject),
512 RecallPastEventEvent(subject=self.subject, object=self.object),
513 BecomeEmbarrassedEvent(subject=self.object),
514 BlushEvent(subject=self.object),
515 SayNoMoreEvent(subject=self.object, object=self.subject),
516 BecomeSadEvent(subject=self.subject),
517 BecomeHappyEvent(subject=self.object),
518 ]
519
520
521 class RomanticTension(PlotDevelopment):
522 templates = (
523 "{subject.def} and {obj.def} do mushy stuff (almost)",
524 )
525
526 def create_events(self):
527 s = self.subject
528 e = [
529 BecomeEmbarrassedEvent(subject=self.subject),
530 PullAsideEvent(subject=self.subject, object=self.object),
531 WantToTalkToYouEvent(subject=self.subject, object=self.object),
532 WhatIsItEvent(subject=self.object, object=self.subject),
533 ]
534 done = False
535 while not done:
536 e.append(EventSequence(
537 FidgetEvent(subject=self.subject, object=self.object),
538 BecomeEmbarrassedEvent(subject=self.object),
539 WhatIsItEvent(subject=self.object, object=self.subject),
540 FacesCloseTogetherEvent(subject=self.object, object=self.subject),
541 ))
542 if random.chance(33):
543 done = True
544 e.append(PreludeToKissEvent(subject=self.subject, object=self.object))
545 return e
546
547
548 class RomanticResolution(PlotDevelopment):
549 templates = (
550 "{subject.def} and {obj.def} do mushy stuff",
551 )
552
553 def create_events(self):
554 s = self.subject
555 e = [
556 BecomeHappyEvent(subject=self.subject),
557 BecomeHappyEvent(subject=self.object),
558 PullAsideEvent(subject=self.subject, object=self.object),
559 WhatIsItEvent(subject=self.object, object=self.subject),
560 MushyStuffEvent(subject=self.subject, object=self.object),
561 OhYouEvent(subject=self.object, object=self.subject),
562 KissEvent(subject=self.object, object=self.subject),
563 AndTheyKissedEvent(subject=self.object, object=self.subject),
564 OhYouEvent(subject=self.object, object=self.subject),
565 ]
566 return e
567
568
569 # - - - -
570
571
572 class Drone(PlotDevelopment):
573 templates = (
574 "{subject.def} is startled by a drone",
575 )
576
577 def create_events(self):
578 s = self.subject
579 return [
580 DroneEvent(subject=s),
581 BecomeAngryEvent(subject=s),
582 WhatWasThatEvent(subject=s),
583 EmoteEvent(subject=s),
584 ]
585
586
587 class ContemplateRock(PlotDevelopment):
588 templates = (
589 "{subject.def} contemplates {obj.indef}",
590 )
591
592 def create_events(self):
593 s = self.subject
594 return [
595 PickUpEvent(subject=s, object=self.object),
596 HoldEvent(subject=s, object=self.object),
597 ContemplateEvent(subject=s, object=self.object),
598 BecomeSadEvent(subject=s),
599 ]
600
601
602 # - - - -
603
604
605 class Journey(PlotDevelopment):
606 templates = (
607 "{subject.def} travel to {obj.def}",
608 )
609
610 def create_events(self):
611 return [
612