1
# -*- test-case-name: imagination.test -*-
5
from zope import interface
6
from zope.interface import Interface, implements
7
from twisted.python.reflect import accumulateMethods
9
from imagination import iimagination
10
from imagination.facets import Facet, IReprable, registerAdapter
11
from imagination.errors import Nonsense, NoSuchObject, RealityException, ActionFailed, ActionRefused, Refusal, LostThing
14
# English-Specific Interfaces
16
class INoun(Interface):
17
"""This interface is intended to specify a specifically English noun.
20
class INoun(Interface):
21
"""An abstract concept that can be expressed in some language.
24
@ivar name: A simple string describing this noun.
28
"""Return a brief string identifier of the language this concept is in.
31
def expressTo(observer):
33
Return a string or unicode object that expresses this concept in some
37
def describe(component, text, priority=1):
38
"""Describe a facet of this object with some text in my native language.
41
def explainTo(observer):
42
"""Return a long description of this object.
45
def knownAs(name, observer):
46
"""Determine whether the given name refers to this object.
49
def changeName(newname):
50
"""Change the name which refers to this object.
54
class IThinker(Interface):
57
XXX Whether or not this thinker recognizes this name. What name??? I dunno!
62
parse and execute a command from the thinker.
69
implements(INoun, IReprable)
71
def __init__(self, original, name = '', description = None, unique = False):
72
Facet.__init__(self, original)
74
self.description = description
76
original[IReprable] = name
78
def describe(self, component, text, priority=1):
79
if self.description is None:
81
for idx in xrange(len(self.description)):
82
p, c, t = self.description[idx]
85
del self.description[idx]
87
self.description[idx] = priority, component, text
90
self.description.append((priority, component, text))
91
self.description.sort()
93
def explainTo(self, observer, component=None):
94
if self.description is None:
96
return express(self.description, observer)
97
#return express([t for p, c, t in self.description if (component is None)
98
# or (c == component)], observer)
100
def getLanguage(self):
103
def expressTo(self, observer):
104
return self.nounPhrase(observer)
106
def hasName(self, name):
107
return name and self.name == name or [
108
None for n in self.name.lower().split()
109
if n.startswith(name.lower())]
111
def knownAs(self, name, observer):
113
if IThinker(observer).recognizes(self, name):
114
# print "using recognizes"
117
# print "using hasName"
118
return self.hasName(name)
120
def changeName(self, newName):
122
# TODO: broadcast a name change event?
124
def article(self, observer):
126
return self.definiteArticle(observer)
128
return self.indefiniteArticle(observer)
130
def indefiniteArticle(self, observer):
131
if self.name[0].lower() in 'aeiou':
135
def definiteArticle(self, observer):
138
def shortName(self, observer):
141
def nounPhrase(self, observer):
142
return (self.article(observer) + self.shortName(observer))
144
def containerPreposition(self, content, observer):
145
"""When this object acts as a container, this is the preposition that
146
objects will be in relation to it."""
149
def containedPhrase(self, observer, containerNoun):
150
# A self is container.preposition the container
151
return "%s is %s %s." % (self.nounPhrase(observer),
152
containerNoun.containerPreposition(self, observer),
153
containerNoun.nounPhrase(observer))
155
def presentPhrase(self, observer):
156
# ugh: this is terrible.
157
if (isinstance(self.original, things.Movable) and
158
isinstance(observer, things.Movable) and
159
self.original.location == observer.location):
160
return "%s is here." % self.nounPhrase(observer)
162
return INoun(self).containedPhrase(self)
164
def heShe(self, observer):
165
if self.original.gender == things.MALE:
167
elif self.original.gender == things.FEMALE:
172
def himHer(self, observer):
173
if self.original.gender == things.MALE:
175
elif self.original.gender == things.FEMALE:
180
def hisHer(self, observer):
181
if self.original.gender == things.MALE:
183
elif self.original.gender == things.FEMALE:
188
class Describability(Facet):
193
def describe(*a): raise NotImplementedError
195
def explainTo(self, observer):
196
if self.description is not None:
197
return self.description
198
noun = INoun(self, default=None)
200
return noun.nounPhrase(observer)
201
return "an nondescript object"
205
class ProperNoun(Noun):
206
def indefiniteArticle(self, observer):
209
def definiteArticle(self, observer):
213
class CollectiveNoun(Noun):
215
def indefiniteArticle(self, observer):
223
class EException(Facet):
225
def expressTo(self, observer):
226
return express(self.original.args, observer)
228
class ENonsense(Facet):
230
errors=["You don't think that you want to waste your time with that.",
231
"There are probably better things to do with your life.",
232
"You are nothing if not creative, but that creativity could be better applied to developing a more productive solution.",
233
"Perhaps that's not such a good idea after all.",
234
"Surely, in this world of limitless possibility, you could think of something better to do.",
235
"An interesting idea...",
236
"A valiant attempt.",
238
"You can't be serious."]
240
def expressTo(self, observer):
241
return random.choice(self.errors)
243
class ENoSuchObject(Facet):
245
def expressTo(self, observer):
246
return express(("You don't see a %r here." % self.original.name), observer)
248
registerAdapter(ENonsense, Nonsense, INoun)
249
registerAdapter(ENoSuchObject, NoSuchObject, INoun)
250
for rex in RealityException, ActionFailed, ActionRefused, Refusal, LostThing:
251
registerAdapter(EException, rex, INoun)
256
"""A subparser is a class that implements some number of parse_<word>
257
methods. These methods will be called by the parser engine for a player.
258
For example, when a player types '<word> foo', the subparser will be
259
invoked (if it implements such a method) with
260
subparser.parse_<word>(player, <text>), where text is the remainder of the
261
sentence after '<word> '.
263
the 'simpleToolParsers' attribute is a dictionary of {"verb_name":
267
simpleToolParsers = {}
268
simpleTargetParsers = {}
269
def getParserBits(self):
271
accumulateMethods(self, dict, "parse_")
272
for parseWord, actionClass in self.simpleToolParsers.items():
273
dict[parseWord] = SimpleToolParser(actionClass).parse
274
for parseWord, actionClass in self.simpleTargetParsers.items():
275
dict[parseWord] = SimpleTargetParser(actionClass).parse
279
class SimpleTargetParser:
281
def __init__(self, actionClass):
282
self.actionClass = actionClass
284
def parse(self, player, text):
285
actor = self.actionClass.getInterface("Actor")(player)
288
return [self.actionClass(actor, text)]
291
class SimpleToolParser:
293
def __init__(self, actionClass):
294
self.actionClass = actionClass
296
def parse(self, player, text):
297
return simpleToolAction(player, self.actionClass, text)
300
class VerbParserEngine:
304
self.spitems = self.subparsers.items()
306
def registerSubparser(self, subparser):
307
bits = subparser.getParserBits()
308
for name, method in bits:
309
name = name.lower().replace("_", " ")
310
self.subparsers.setdefault(name, []).append(method)
311
self.spitems = self.subparsers.items()
313
def parseToActions(self, player, text):
314
"""Dispatch to all appropriate subparsers.
316
# note, this is slightly buggy
317
# look_up shouldn't be different than look__up but the work necessary
318
# to equalize them just doesn't seem worth it.
319
matchtext = text.lower() #.replace(' ', '_')
320
potentialActions = []
321
for prefx, methods in self.spitems:
322
if matchtext.startswith(prefx):
323
subtext = text[len(prefx):].strip()
324
for method in methods:
325
potact = method(player, subtext)
327
potentialActions.extend(potact)
328
return potentialActions
332
theParserEngine = VerbParserEngine()
333
registerSubparser = theParserEngine.registerSubparser
334
parseToActions = theParserEngine.parseToActions
336
class Parsing(Facet):
338
potentialActions = None
341
def __init__(self, *args, **kwargs):
342
Facet.__init__(self, *args, **kwargs)
344
self.potentialThings = []
346
def recognizes(self, noun, name):
350
def parse(self, text):
353
My return values are only for debugging purposes.
355
potentialActions = self.parseToActions(self.original, text)
357
if not potentialActions:
358
raise Nonsense("That doesn't make sense!")
360
#argh, we're relying on getAmbiguities' side-effects.
361
for act in potentialActions:
364
potentialRealActions = [act for act in potentialActions if not act.isPlaceholder()]
366
if (len(potentialRealActions) and
367
(len(potentialRealActions) < len(potentialActions))):
368
potentialActions = potentialRealActions
370
if len(potentialActions) == 1:
371
# Command was not ambiguous Action-wise
372
return self._runAction(potentialActions[0])
374
# More than one Action!
375
return self.askForAction(potentialActions
376
).addCallback(self._runAction
379
def _runAction(self, action):
380
# print '[][][][][][][][][][][][][][][][]'
381
potentialThings = action.getAmbiguities()
382
# print '][][][][][][][][][][][][][][][]['
383
# print 'Lala', potentialThings
386
# It _was_ ambiguous wrt the Things involved
387
return self.askForThing(potentialThings, action)
389
#No ambiguities! Woohoo
390
return action.performAction()
392
def parseToActions(self, original, text):
393
return parseToActions(original, text)
395
def askForAction(self, potentialActions):
396
return iimagination.IUI(self.original).presentMenu(potentialActions
397
).addCallbacks(potentialActions.__getitem__, lambda x: None)
399
def askForThing(self, potentialThings, action):
401
def getThing(result):
402
thing = pt[0][1][result]
403
return self.doneResolvingThing(thing, potentialThings, action)
404
return iimagination.IUI(self.original).presentMenu(pt[0][1], pt[0][0]
405
).addCallbacks(getThing, lambda x: None)
407
def doneResolvingThing(self, thing, potentialThings, action):
408
self.resolved.append((potentialThings[0][0], thing))
409
del potentialThings[0]
411
return self.askForThing(potentialThings, action)
413
#RESOLVED ALL THINGS!
414
for iface, thing in self.resolved:
415
action.setImplementor(iface, thing)
416
return action.performAction()
419
### Utility Functions
422
"""Split on a delimiter found backwards through a string.
424
Returns a 2-tuple of (before-last-sep, after-last-sep). If sep is not
425
found, it returns (s,'').
430
return (s[:n].strip(), s[n+len(sep):].strip())
432
def _simpleToolact(player, toolActionClass, targetName, toolName):
433
return [toolActionClass(player, targetName, toolName)]
435
def simpleToolAction(player, toolActionClass, text):
437
# gun is the tool, bob is the target
438
1) 'shoot bob with gun'
439
# still the same, though 'at' reverses the order
440
2) 'shoot gun at bob'
441
# the gun is the tool; north is the target. Do I need to handle this case?
444
### The action is going to have to deal properly with implicit (None)
445
### targets and tools, and locate them itself, either in the constructor or
446
### in processAction. (Some actions might not even need both tool and
449
# implicit tool (yuck)
451
# implicit _target_ (extra yuck)
455
actor = toolActionClass.getInterface("Actor")(player)
458
if text.count(' with '): # case #1
459
bobName, gunName = rsplit1(text, ' with ')
460
return _simpleToolact(player, toolActionClass, bobName, gunName)
462
# XXX What is the use-case for ' and '?
463
for splitter in (' at ', ' to ', ' from ', ' and '):
465
gunName, bobName = rsplit1(text, splitter)
466
return _simpleToolact(player, toolActionClass, bobName, gunName)
467
# you rolled a 9 (case #4 & 5)
469
if toolActionClass.allowImplicitTarget:
470
lr.extend(_simpleToolact(player, toolActionClass, None, text))
472
lr.extend(_simpleToolact(player, toolActionClass, text, None))
477
class _ExpressSeq(Facet):
478
def expressTo(self, observer):
479
return ''.join([INoun(x).expressTo(observer) for x in self.original])
481
class _ExpressNothing(Facet):
482
def expressTo(self, observer):
485
class _ExpressYourself(Facet):
486
def expressTo(self, observer):
489
registerAdapter(_ExpressSeq, tuple, INoun)
490
registerAdapter(_ExpressSeq, list, INoun)
491
registerAdapter(_ExpressNothing, type(None), INoun)
492
registerAdapter(_ExpressYourself, str, INoun)
494
def express(tup, obs, iface=INoun):
495
return iface(tup).expressTo(obs)