1
# -*- test-case-name: imaginary.test.test_concept -*-
5
Textual formatting for game objects.
9
from string import Formatter
11
from zope.interface import implements, implementer
13
from twisted.python.components import registerAdapter
15
from epsilon import structlike
17
from imaginary import iimaginary, iterutils, text as T
32
This is a language wrapper around a Thing.
34
It is separated into its own class for two reasons:
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.
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
51
def __init__(self, thing):
56
return ExpressString(self.thing.name)
61
return self.shortName()
62
return ExpressList([self.indefiniteArticle(), self.shortName()])
65
def definiteNounPhrase(self):
67
return self.shortName()
68
return ExpressList([self.definiteArticle(), self.shortName()])
71
def indefiniteArticle(self):
72
# XXX TODO FIXME: YTTRIUM
73
if self.thing.name[0].lower() in u'aeiou':
78
def definiteArticle(self):
84
Return the personal pronoun for the wrapped thing.
86
x = {Gender.MALE: u'he',
88
}.get(self.thing.gender, u'it')
89
return ExpressString(x)
94
Return the objective pronoun for the wrapped thing.
96
x = {Gender.MALE: u'him',
98
}.get(self.thing.gender, u'it')
99
return ExpressString(x)
104
Return a possessive pronoun that cannot be used after 'is'.
106
x = {Gender.MALE: u'his',
107
Gender.FEMALE: u'her' # <-- OMG! hers!
108
}.get(self.thing.gender, u'its')
109
return ExpressString(x)
112
#FIXME: add his/hers LATER
116
def flattenWithoutColors(vt102):
117
return T.flatten(vt102, useColors=False)
120
@implementer(iimaginary.IConcept)
121
class BaseExpress(object):
123
def __init__(self, original):
124
self.original = original
127
def plaintext(self, observer):
128
return flattenWithoutColors(self.vt102(observer))
132
class DescriptionConcept(structlike.record('name description exits others',
133
description=u"", exits=(), others=())):
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.
138
Concepts will be ordered by the C{preferredOrder} class attribute.
139
Concepts not named in this list will appear last in an unpredictable
142
@ivar name: The name of the thing being described.
144
@ivar description: A basic description of the thing being described, the
145
first thing to show up.
147
@ivar exits: An iterable of L{IExit}, to be listed as exits in the
150
@ivar others: An iterable of L{IDescriptionContributor} that will
151
supplement the description.
153
implements(iimaginary.IConcept)
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
160
preferredOrder = ['ExpressCondition',
162
'ExpressSurroundings',
165
def plaintext(self, observer):
166
return flattenWithoutColors(self.vt102(observer))
169
def vt102(self, observer):
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))],
178
description = self.description or u""
180
description = (T.fg.green, self.description, u'\n')
182
descriptionConcepts = []
184
for pup in self.others:
185
descriptionConcepts.append(pup.conceptualize())
189
return self.preferredOrder.index(c.__class__.__name__)
191
# Anything unrecognized goes after anything recognized.
192
return len(self.preferredOrder)
194
descriptionConcepts.sort(key=index)
196
descriptionComponents = []
197
for c in descriptionConcepts:
198
s = c.vt102(observer)
200
descriptionComponents.extend([s, u'\n'])
202
if descriptionComponents:
203
descriptionComponents.pop()
206
[T.bold, T.fg.green, u'[ ', [T.fg.normal, self.name], u' ]\n'],
209
descriptionComponents]
213
class ExpressNumber(BaseExpress):
214
implements(iimaginary.IConcept)
216
def vt102(self, observer):
217
return str(self.original)
221
class ExpressString(BaseExpress):
222
implements(iimaginary.IConcept)
224
def __init__(self, original, capitalized=False):
225
self.original = original
226
self._cap = capitalized
229
def vt102(self, observer):
231
return self.original[:1].upper() + self.original[1:]
235
def capitalizeConcept(self):
236
return ExpressString(self.original, True)
240
class ExpressList(BaseExpress):
241
implements(iimaginary.IConcept)
243
def concepts(self, observer):
244
return map(iimaginary.IConcept, self.original)
246
def vt102(self, observer):
247
return [x.vt102(observer) for x in self.concepts(observer)]
249
def capitalizeConcept(self):
250
return Sentence(self.original)
255
class Sentence(ExpressList):
256
def vt102(self, observer):
257
o = self.concepts(observer)
259
o[0] = o[0].capitalizeConcept()
260
return [x.vt102(observer) for x in o]
263
def capitalizeConcept(self):
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)
277
class ItemizedList(BaseExpress):
278
implements(iimaginary.IConcept)
280
def __init__(self, listOfConcepts):
281
self.listOfConcepts = listOfConcepts
284
def concepts(self, observer):
285
return self.listOfConcepts
288
def vt102(self, observer):
290
itemizedStringList(self.concepts(observer))).vt102(observer)
293
def capitalizeConcept(self):
294
listOfConcepts = self.listOfConcepts[:]
296
listOfConcepts[0] = iimaginary.IConcept(listOfConcepts[0]).capitalizeConcept()
297
return ItemizedList(listOfConcepts)
301
def itemizedStringList(desc):
309
for ele in desc[:-1]:
317
class ConceptTemplate(object):
319
A L{ConceptTemplate} wraps a text template which may intersperse literal
320
strings with markers for substitution.
322
Substitution markers follow U{the syntax for str.format<http://docs.python.org/2/library/string.html#format-string-syntax>}.
324
Values for field names are supplied to the L{expand} method.
326
def __init__(self, templateText):
328
@param templateText: The text of the template. For example,
329
C{u"Hello, {target:name}."}.
330
@type templateText: L{unicode}
332
self.templateText = templateText
335
def expand(self, values):
337
Generate concepts based on the template.
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}
347
@return: An iterator the combined elements of which represent the
348
result of expansion of the template. The elements are adaptable to
351
parts = Formatter().parse(self.templateText)
352
for (literalText, fieldName, formatSpec, conversion) in parts:
354
yield ExpressString(literalText)
357
target = values[fieldName.lower()]
361
extra = u" '%s'" % (formatSpec,)
362
yield u"<missing target '%s' for%s expansion>" % (
366
# A nice enhancement would be to delegate this logic to target
368
expander = getattr(self, '_expand_' + formatSpec.upper())
369
except AttributeError:
370
yield u"<'%s' unsupported by target '%s'>" % (
371
formatSpec, fieldName)
373
yield expander(target)
378
def _expand_NAME(self, target):
380
Get the name of a L{Thing}.
385
def _expand_PRONOUN(self, target):
387
Get the personal pronoun of a L{Thing}.
389
return Noun(target).heShe()