~divmod-dev/divmod.org/trunk

« back to all changes in this revision

Viewing changes to Imaginary/imaginary/language.py

  • Committer: Jean-Paul Calderone
  • Date: 2014-06-07 13:26:27 UTC
  • mfrom: (2741.2.2 1320678-remove-imaginary)
  • Revision ID: exarkun@twistedmatrix.com-20140607132627-jz2zhbk9jajamk43
Remove Imaginary.  Find it at <https://github.com/twisted/imaginary>.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- test-case-name: imaginary.test.test_concept -*-
2
 
 
3
 
"""
4
 
 
5
 
Textual formatting for game objects.
6
 
 
7
 
"""
8
 
import types
9
 
from string import Formatter
10
 
 
11
 
from zope.interface import implements, implementer
12
 
 
13
 
from twisted.python.components import registerAdapter
14
 
 
15
 
from epsilon import structlike
16
 
 
17
 
from imaginary import iimaginary, iterutils, text as T
18
 
 
19
 
 
20
 
class Gender(object):
21
 
    """
22
 
    enum!
23
 
    """
24
 
    MALE = 1
25
 
    FEMALE = 2
26
 
    NEUTER = 3
27
 
 
28
 
 
29
 
 
30
 
class Noun(object):
31
 
    """
32
 
    This is a language wrapper around a Thing.
33
 
 
34
 
    It is separated into its own class for two reasons:
35
 
 
36
 
     - You should try to keep your game-logic self-contained and avoid
37
 
       polluting it with lots of constant strings, so that porting to new
38
 
       interfaces (text prototype -> isometric final implementation) is easy.
39
 
       It's easier to read the code that way and make changes to the logic even
40
 
       if you don't want to move to a different interface.
41
 
 
42
 
 
43
 
     - It would be nice if text games could be internationalized by separating
44
 
       the formatting logic from the game logic.  In an extreme case, it would
45
 
       be SUPER-COOL if people could be playing the same game in french and
46
 
       english on the same server, simply by changing a setting on their
47
 
       client.
48
 
    """
49
 
 
50
 
 
51
 
    def __init__(self, thing):
52
 
        self.thing = thing
53
 
 
54
 
 
55
 
    def shortName(self):
56
 
        return ExpressString(self.thing.name)
57
 
 
58
 
 
59
 
    def nounPhrase(self):
60
 
        if self.thing.proper:
61
 
            return self.shortName()
62
 
        return ExpressList([self.indefiniteArticle(), self.shortName()])
63
 
 
64
 
 
65
 
    def definiteNounPhrase(self):
66
 
        if self.thing.proper:
67
 
            return self.shortName()
68
 
        return ExpressList([self.definiteArticle(), self.shortName()])
69
 
 
70
 
 
71
 
    def indefiniteArticle(self):
72
 
        # XXX TODO FIXME: YTTRIUM
73
 
        if self.thing.name[0].lower() in u'aeiou':
74
 
            return u'an '
75
 
        return u'a '
76
 
 
77
 
 
78
 
    def definiteArticle(self):
79
 
        return u'the '
80
 
 
81
 
 
82
 
    def heShe(self):
83
 
        """
84
 
        Return the personal pronoun for the wrapped thing.
85
 
        """
86
 
        x = {Gender.MALE: u'he',
87
 
             Gender.FEMALE: u'she'
88
 
             }.get(self.thing.gender, u'it')
89
 
        return ExpressString(x)
90
 
 
91
 
 
92
 
    def himHer(self):
93
 
        """
94
 
        Return the objective pronoun for the wrapped thing.
95
 
        """
96
 
        x = {Gender.MALE: u'him',
97
 
             Gender.FEMALE: u'her'
98
 
             }.get(self.thing.gender, u'it')
99
 
        return ExpressString(x)
100
 
 
101
 
 
102
 
    def hisHer(self):
103
 
        """
104
 
        Return a possessive pronoun that cannot be used after 'is'.
105
 
        """
106
 
        x = {Gender.MALE: u'his',
107
 
             Gender.FEMALE: u'her' # <-- OMG! hers!
108
 
             }.get(self.thing.gender, u'its')
109
 
        return ExpressString(x)
110
 
 
111
 
 
112
 
    #FIXME: add his/hers LATER
113
 
 
114
 
 
115
 
 
116
 
def flattenWithoutColors(vt102):
117
 
    return T.flatten(vt102, useColors=False)
118
 
 
119
 
 
120
 
@implementer(iimaginary.IConcept)
121
 
class BaseExpress(object):
122
 
 
123
 
    def __init__(self, original):
124
 
        self.original = original
125
 
 
126
 
 
127
 
    def plaintext(self, observer):
128
 
        return flattenWithoutColors(self.vt102(observer))
129
 
 
130
 
 
131
 
 
132
 
class DescriptionConcept(structlike.record('name description exits others',
133
 
                                           description=u"", exits=(), others=())):
134
 
    """
135
 
    A concept which is expressed as the description of a Thing as well as
136
 
    any concepts which power up that thing for IDescriptionContributor.
137
 
 
138
 
    Concepts will be ordered by the C{preferredOrder} class attribute.
139
 
    Concepts not named in this list will appear last in an unpredictable
140
 
    order.
141
 
 
142
 
    @ivar name: The name of the thing being described.
143
 
 
144
 
    @ivar description: A basic description of the thing being described, the
145
 
        first thing to show up.
146
 
 
147
 
    @ivar exits: An iterable of L{IExit}, to be listed as exits in the
148
 
        description.
149
 
 
150
 
    @ivar others: An iterable of L{IDescriptionContributor} that will
151
 
        supplement the description.
152
 
    """
153
 
    implements(iimaginary.IConcept)
154
 
 
155
 
    # This may not be the most awesome solution to the ordering problem, but
156
 
    # it is the best I can think of right now.  This is strictly a
157
 
    # user-interface level problem.  Only the ordering in the string output
158
 
    # send to the user should depend on this; nothing in the world should be
159
 
    # affected.
160
 
    preferredOrder = ['ExpressCondition',
161
 
                      'ExpressClothing',
162
 
                      'ExpressSurroundings',
163
 
                      ]
164
 
 
165
 
    def plaintext(self, observer):
166
 
        return flattenWithoutColors(self.vt102(observer))
167
 
 
168
 
 
169
 
    def vt102(self, observer):
170
 
        exits = u''
171
 
        if self.exits:
172
 
            exits = [T.bold, T.fg.green, u'( ',
173
 
                     [T.fg.normal, T.fg.yellow,
174
 
                      iterutils.interlace(u' ',
175
 
                                          (exit.name for exit in self.exits))],
176
 
                     u' )', u'\n']
177
 
 
178
 
        description = self.description or u""
179
 
        if description:
180
 
            description = (T.fg.green, self.description, u'\n')
181
 
 
182
 
        descriptionConcepts = []
183
 
 
184
 
        for pup in self.others:
185
 
            descriptionConcepts.append(pup.conceptualize())
186
 
 
187
 
        def index(c):
188
 
            try:
189
 
                return self.preferredOrder.index(c.__class__.__name__)
190
 
            except ValueError:
191
 
                # Anything unrecognized goes after anything recognized.
192
 
                return len(self.preferredOrder)
193
 
 
194
 
        descriptionConcepts.sort(key=index)
195
 
 
196
 
        descriptionComponents = []
197
 
        for c in descriptionConcepts:
198
 
            s = c.vt102(observer)
199
 
            if s:
200
 
                descriptionComponents.extend([s, u'\n'])
201
 
 
202
 
        if descriptionComponents:
203
 
            descriptionComponents.pop()
204
 
 
205
 
        return [
206
 
            [T.bold, T.fg.green, u'[ ', [T.fg.normal, self.name], u' ]\n'],
207
 
            exits,
208
 
            description,
209
 
            descriptionComponents]
210
 
 
211
 
 
212
 
 
213
 
class ExpressNumber(BaseExpress):
214
 
    implements(iimaginary.IConcept)
215
 
 
216
 
    def vt102(self, observer):
217
 
        return str(self.original)
218
 
 
219
 
 
220
 
 
221
 
class ExpressString(BaseExpress):
222
 
    implements(iimaginary.IConcept)
223
 
 
224
 
    def __init__(self, original, capitalized=False):
225
 
        self.original = original
226
 
        self._cap = capitalized
227
 
 
228
 
 
229
 
    def vt102(self, observer):
230
 
        if self._cap:
231
 
            return self.original[:1].upper() + self.original[1:]
232
 
        return self.original
233
 
 
234
 
 
235
 
    def capitalizeConcept(self):
236
 
        return ExpressString(self.original, True)
237
 
 
238
 
 
239
 
 
240
 
class ExpressList(BaseExpress):
241
 
    implements(iimaginary.IConcept)
242
 
 
243
 
    def concepts(self, observer):
244
 
        return map(iimaginary.IConcept, self.original)
245
 
 
246
 
    def vt102(self, observer):
247
 
        return [x.vt102(observer) for x in self.concepts(observer)]
248
 
 
249
 
    def capitalizeConcept(self):
250
 
        return Sentence(self.original)
251
 
 
252
 
 
253
 
 
254
 
 
255
 
class Sentence(ExpressList):
256
 
    def vt102(self, observer):
257
 
        o = self.concepts(observer)
258
 
        if o:
259
 
            o[0] = o[0].capitalizeConcept()
260
 
        return [x.vt102(observer) for x in o]
261
 
 
262
 
 
263
 
    def capitalizeConcept(self):
264
 
        return self
265
 
 
266
 
 
267
 
 
268
 
registerAdapter(ExpressNumber, int, iimaginary.IConcept)
269
 
registerAdapter(ExpressNumber, long, iimaginary.IConcept)
270
 
registerAdapter(ExpressString, str, iimaginary.IConcept)
271
 
registerAdapter(ExpressString, unicode, iimaginary.IConcept)
272
 
registerAdapter(ExpressList, list, iimaginary.IConcept)
273
 
registerAdapter(ExpressList, tuple, iimaginary.IConcept)
274
 
registerAdapter(ExpressList, types.GeneratorType, iimaginary.IConcept)
275
 
 
276
 
 
277
 
class ItemizedList(BaseExpress):
278
 
    implements(iimaginary.IConcept)
279
 
 
280
 
    def __init__(self, listOfConcepts):
281
 
        self.listOfConcepts = listOfConcepts
282
 
 
283
 
 
284
 
    def concepts(self, observer):
285
 
        return self.listOfConcepts
286
 
 
287
 
 
288
 
    def vt102(self, observer):
289
 
        return ExpressList(
290
 
            itemizedStringList(self.concepts(observer))).vt102(observer)
291
 
 
292
 
 
293
 
    def capitalizeConcept(self):
294
 
        listOfConcepts = self.listOfConcepts[:]
295
 
        if listOfConcepts:
296
 
            listOfConcepts[0] = iimaginary.IConcept(listOfConcepts[0]).capitalizeConcept()
297
 
        return ItemizedList(listOfConcepts)
298
 
 
299
 
 
300
 
 
301
 
def itemizedStringList(desc):
302
 
    if len(desc) == 1:
303
 
        yield desc[0]
304
 
    elif len(desc) == 2:
305
 
        yield desc[0]
306
 
        yield u' and '
307
 
        yield desc[1]
308
 
    elif len(desc) > 2:
309
 
        for ele in desc[:-1]:
310
 
            yield ele
311
 
            yield u', '
312
 
        yield u'and '
313
 
        yield desc[-1]
314
 
 
315
 
 
316
 
 
317
 
class ConceptTemplate(object):
318
 
    """
319
 
    A L{ConceptTemplate} wraps a text template which may intersperse literal
320
 
    strings with markers for substitution.
321
 
 
322
 
    Substitution markers follow U{the syntax for str.format<http://docs.python.org/2/library/string.html#format-string-syntax>}.
323
 
 
324
 
    Values for field names are supplied to the L{expand} method.
325
 
    """
326
 
    def __init__(self, templateText):
327
 
        """
328
 
        @param templateText: The text of the template.  For example,
329
 
            C{u"Hello, {target:name}."}.
330
 
        @type templateText: L{unicode}
331
 
        """
332
 
        self.templateText = templateText
333
 
 
334
 
 
335
 
    def expand(self, values):
336
 
        """
337
 
        Generate concepts based on the template.
338
 
 
339
 
        @param values: A L{dict} mapping substitution markers to application
340
 
            objects from which to take values for those substitutions.  For
341
 
            example, a key might be C{u"target"}.  The associated value will be
342
 
            sustituted each place C{u"{target}"} appears in the template
343
 
            string.  Or, the value's name will be substituted each place
344
 
            C{u"{target:name}"} appears in the template string.
345
 
        @type values: L{dict} mapping L{unicode} to L{object}
346
 
 
347
 
        @return: An iterator the combined elements of which represent the
348
 
            result of expansion of the template.  The elements are adaptable to
349
 
            L{IConcept}.
350
 
        """
351
 
        parts = Formatter().parse(self.templateText)
352
 
        for (literalText, fieldName, formatSpec, conversion) in parts:
353
 
            if literalText:
354
 
                yield ExpressString(literalText)
355
 
            if fieldName:
356
 
                try:
357
 
                    target = values[fieldName.lower()]
358
 
                except KeyError:
359
 
                    extra = u""
360
 
                    if formatSpec:
361
 
                        extra = u" '%s'" % (formatSpec,)
362
 
                    yield u"<missing target '%s' for%s expansion>" % (
363
 
                        fieldName, extra)
364
 
                else:
365
 
                    if formatSpec:
366
 
                        # A nice enhancement would be to delegate this logic to target
367
 
                        try:
368
 
                            expander = getattr(self, '_expand_' + formatSpec.upper())
369
 
                        except AttributeError:
370
 
                            yield u"<'%s' unsupported by target '%s'>" % (
371
 
                                formatSpec, fieldName)
372
 
                        else:
373
 
                            yield expander(target)
374
 
                    else:
375
 
                        yield target
376
 
 
377
 
 
378
 
    def _expand_NAME(self, target):
379
 
        """
380
 
        Get the name of a L{Thing}.
381
 
        """
382
 
        return target.name
383
 
 
384
 
 
385
 
    def _expand_PRONOUN(self, target):
386
 
        """
387
 
        Get the personal pronoun of a L{Thing}.
388
 
        """
389
 
        return Noun(target).heShe()