adventureEngine.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. # adventureEngine.py
  2. # Copyright 2005-2006, Paul McGuire
  3. #
  4. # Updated 2012 - latest pyparsing API
  5. #
  6. from pyparsing import *
  7. import random
  8. import string
  9. def aOrAn( item ):
  10. if item.desc[0] in "aeiou":
  11. return "an " + item.desc
  12. else:
  13. return "a " + item.desc
  14. def enumerateItems(l):
  15. if len(l) == 0: return "nothing"
  16. out = []
  17. if len(l) > 1:
  18. out.append(', '.join(aOrAn(item) for item in l[:-1]))
  19. out.append('and')
  20. out.append(aOrAn(l[-1]))
  21. return " ".join(out)
  22. def enumerateDoors(l):
  23. if len(l) == 0: return ""
  24. out = []
  25. if len(l) > 1:
  26. out.append(', '.join(l[:-1]))
  27. out.append("and")
  28. out.append(l[-1])
  29. return " ".join(out)
  30. class Room(object):
  31. def __init__(self, desc):
  32. self.desc = desc
  33. self.inv = []
  34. self.gameOver = False
  35. self.doors = [None,None,None,None]
  36. def __getattr__(self,attr):
  37. return \
  38. {
  39. "n":self.doors[0],
  40. "s":self.doors[1],
  41. "e":self.doors[2],
  42. "w":self.doors[3],
  43. }[attr]
  44. def enter(self,player):
  45. if self.gameOver:
  46. player.gameOver = True
  47. def addItem(self, it):
  48. self.inv.append(it)
  49. def removeItem(self,it):
  50. self.inv.remove(it)
  51. def describe(self):
  52. print(self.desc)
  53. visibleItems = [ it for it in self.inv if it.isVisible ]
  54. if random.random() > 0.5:
  55. if len(visibleItems) > 1:
  56. is_form = "are"
  57. else:
  58. is_form = "is"
  59. print("There {0} {1} here.".format(is_form, enumerateItems(visibleItems)))
  60. else:
  61. print("You see %s." % (enumerateItems(visibleItems)))
  62. class Exit(Room):
  63. def __init__(self):
  64. super(Exit,self).__init__("")
  65. def enter(self,player):
  66. player.gameOver = True
  67. class Item(object):
  68. items = {}
  69. def __init__(self, desc):
  70. self.desc = desc
  71. self.isDeadly = False
  72. self.isFragile = False
  73. self.isBroken = False
  74. self.isTakeable = True
  75. self.isVisible = True
  76. self.isOpenable = False
  77. self.useAction = None
  78. self.usableConditionTest = None
  79. self.cantTakeMessage = "You can't take that!"
  80. Item.items[desc] = self
  81. def __str__(self):
  82. return self.desc
  83. def breakItem(self):
  84. if not self.isBroken:
  85. print("<Crash!>")
  86. self.desc = "broken " + self.desc
  87. self.isBroken = True
  88. def isUsable(self, player, target):
  89. if self.usableConditionTest:
  90. return self.usableConditionTest( player, target )
  91. else:
  92. return False
  93. def useItem(self, player, target):
  94. if self.useAction:
  95. self.useAction(player, self, target)
  96. class OpenableItem(Item):
  97. def __init__(self, desc, contents=None):
  98. super(OpenableItem,self).__init__(desc)
  99. self.isOpenable = True
  100. self.isOpened = False
  101. if contents is not None:
  102. if isinstance(contents, Item):
  103. self.contents = [contents,]
  104. else:
  105. self.contents = contents
  106. else:
  107. self.contents = []
  108. def openItem(self, player):
  109. if not self.isOpened:
  110. self.isOpened = not self.isOpened
  111. if self.contents is not None:
  112. for item in self.contents:
  113. player.room.addItem( item )
  114. self.contents = []
  115. self.desc = "open " + self.desc
  116. def closeItem(self, player):
  117. if self.isOpened:
  118. self.isOpened = not self.isOpened
  119. if self.desc.startswith("open "):
  120. self.desc = self.desc[5:]
  121. class Command(object):
  122. "Base class for commands"
  123. def __init__(self, verb, verbProg):
  124. self.verb = verb
  125. self.verbProg = verbProg
  126. @staticmethod
  127. def helpDescription():
  128. return ""
  129. def _doCommand(self, player):
  130. pass
  131. def __call__(self, player ):
  132. print(self.verbProg.capitalize()+"...")
  133. self._doCommand(player)
  134. class MoveCommand(Command):
  135. def __init__(self, quals):
  136. super(MoveCommand,self).__init__("MOVE", "moving")
  137. self.direction = quals.direction[0]
  138. @staticmethod
  139. def helpDescription():
  140. return """MOVE or GO - go NORTH, SOUTH, EAST, or WEST
  141. (can abbreviate as 'GO N' and 'GO W', or even just 'E' and 'S')"""
  142. def _doCommand(self, player):
  143. rm = player.room
  144. nextRoom = rm.doors[
  145. {
  146. "N":0,
  147. "S":1,
  148. "E":2,
  149. "W":3,
  150. }[self.direction]
  151. ]
  152. if nextRoom:
  153. player.moveTo( nextRoom )
  154. else:
  155. print("Can't go that way.")
  156. class TakeCommand(Command):
  157. def __init__(self, quals):
  158. super(TakeCommand,self).__init__("TAKE", "taking")
  159. self.subject = quals.item
  160. @staticmethod
  161. def helpDescription():
  162. return "TAKE or PICKUP or PICK UP - pick up an object (but some are deadly)"
  163. def _doCommand(self, player):
  164. rm = player.room
  165. subj = Item.items[self.subject]
  166. if subj in rm.inv and subj.isVisible:
  167. if subj.isTakeable:
  168. rm.removeItem(subj)
  169. player.take(subj)
  170. else:
  171. print(subj.cantTakeMessage)
  172. else:
  173. print("There is no %s here." % subj)
  174. class DropCommand(Command):
  175. def __init__(self, quals):
  176. super(DropCommand,self).__init__("DROP", "dropping")
  177. self.subject = quals.item
  178. @staticmethod
  179. def helpDescription():
  180. return "DROP or LEAVE - drop an object (but fragile items may break)"
  181. def _doCommand(self, player):
  182. rm = player.room
  183. subj = Item.items[self.subject]
  184. if subj in player.inv:
  185. rm.addItem(subj)
  186. player.drop(subj)
  187. else:
  188. print("You don't have %s." % (aOrAn(subj)))
  189. class InventoryCommand(Command):
  190. def __init__(self, quals):
  191. super(InventoryCommand,self).__init__("INV", "taking inventory")
  192. @staticmethod
  193. def helpDescription():
  194. return "INVENTORY or INV or I - lists what items you have"
  195. def _doCommand(self, player):
  196. print("You have %s." % enumerateItems( player.inv ))
  197. class LookCommand(Command):
  198. def __init__(self, quals):
  199. super(LookCommand,self).__init__("LOOK", "looking")
  200. @staticmethod
  201. def helpDescription():
  202. return "LOOK or L - describes the current room and any objects in it"
  203. def _doCommand(self, player):
  204. player.room.describe()
  205. class DoorsCommand(Command):
  206. def __init__(self, quals):
  207. super(DoorsCommand,self).__init__("DOORS", "looking for doors")
  208. @staticmethod
  209. def helpDescription():
  210. return "DOORS - display what doors are visible from this room"
  211. def _doCommand(self, player):
  212. rm = player.room
  213. numDoors = sum([1 for r in rm.doors if r is not None])
  214. if numDoors == 0:
  215. reply = "There are no doors in any direction."
  216. else:
  217. if numDoors == 1:
  218. reply = "There is a door to the "
  219. else:
  220. reply = "There are doors to the "
  221. doorNames = [ {0:"north", 1:"south", 2:"east", 3:"west"}[i]
  222. for i,d in enumerate(rm.doors) if d is not None ]
  223. #~ print doorNames
  224. reply += enumerateDoors( doorNames )
  225. reply += "."
  226. print(reply)
  227. class UseCommand(Command):
  228. def __init__(self, quals):
  229. super(UseCommand,self).__init__("USE", "using")
  230. self.subject = Item.items[quals.usedObj]
  231. if quals.targetObj:
  232. self.target = Item.items[quals.targetObj]
  233. else:
  234. self.target = None
  235. @staticmethod
  236. def helpDescription():
  237. return "USE or U - use an object, optionally IN or ON another object"
  238. def _doCommand(self, player):
  239. rm = player.room
  240. availItems = rm.inv + player.inv
  241. if self.subject in availItems:
  242. if self.subject.isUsable( player, self.target ):
  243. self.subject.useItem( player, self.target )
  244. else:
  245. print("You can't use that here.")
  246. else:
  247. print("There is no %s here to use." % self.subject)
  248. class OpenCommand(Command):
  249. def __init__(self, quals):
  250. super(OpenCommand,self).__init__("OPEN", "opening")
  251. self.subject = Item.items[quals.item]
  252. @staticmethod
  253. def helpDescription():
  254. return "OPEN or O - open an object"
  255. def _doCommand(self, player):
  256. rm = player.room
  257. availItems = rm.inv+player.inv
  258. if self.subject in availItems:
  259. if self.subject.isOpenable:
  260. if not self.subject.isOpened:
  261. self.subject.openItem( player )
  262. else:
  263. print("It's already open.")
  264. else:
  265. print("You can't open that.")
  266. else:
  267. print("There is no %s here to open." % self.subject)
  268. class CloseCommand(Command):
  269. def __init__(self, quals):
  270. super(CloseCommand,self).__init__("CLOSE", "closing")
  271. self.subject = Item.items[quals.item]
  272. @staticmethod
  273. def helpDescription():
  274. return "CLOSE or CL - close an object"
  275. def _doCommand(self, player):
  276. rm = player.room
  277. availItems = rm.inv+player.inv
  278. if self.subject in availItems:
  279. if self.subject.isOpenable:
  280. if self.subject.isOpened:
  281. self.subject.closeItem( player )
  282. else:
  283. print("You can't close that, it's not open.")
  284. else:
  285. print("You can't close that.")
  286. else:
  287. print("There is no %s here to close." % self.subject)
  288. class QuitCommand(Command):
  289. def __init__(self, quals):
  290. super(QuitCommand,self).__init__("QUIT", "quitting")
  291. @staticmethod
  292. def helpDescription():
  293. return "QUIT or Q - ends the game"
  294. def _doCommand(self, player):
  295. print("Ok....")
  296. player.gameOver = True
  297. class HelpCommand(Command):
  298. def __init__(self, quals):
  299. super(HelpCommand,self).__init__("HELP", "helping")
  300. @staticmethod
  301. def helpDescription():
  302. return "HELP or H or ? - displays this help message"
  303. def _doCommand(self, player):
  304. print("Enter any of the following commands (not case sensitive):")
  305. for cmd in [
  306. InventoryCommand,
  307. DropCommand,
  308. TakeCommand,
  309. UseCommand,
  310. OpenCommand,
  311. CloseCommand,
  312. MoveCommand,
  313. LookCommand,
  314. DoorsCommand,
  315. QuitCommand,
  316. HelpCommand,
  317. ]:
  318. print(" - %s" % cmd.helpDescription())
  319. print()
  320. class AppParseException(ParseException):
  321. pass
  322. class Parser(object):
  323. def __init__(self):
  324. self.bnf = self.makeBNF()
  325. def makeBNF(self):
  326. invVerb = oneOf("INV INVENTORY I", caseless=True)
  327. dropVerb = oneOf("DROP LEAVE", caseless=True)
  328. takeVerb = oneOf("TAKE PICKUP", caseless=True) | \
  329. (CaselessLiteral("PICK") + CaselessLiteral("UP") )
  330. moveVerb = oneOf("MOVE GO", caseless=True) | empty
  331. useVerb = oneOf("USE U", caseless=True)
  332. openVerb = oneOf("OPEN O", caseless=True)
  333. closeVerb = oneOf("CLOSE CL", caseless=True)
  334. quitVerb = oneOf("QUIT Q", caseless=True)
  335. lookVerb = oneOf("LOOK L", caseless=True)
  336. doorsVerb = CaselessLiteral("DOORS")
  337. helpVerb = oneOf("H HELP ?",caseless=True)
  338. itemRef = OneOrMore(Word(alphas)).setParseAction( self.validateItemName )
  339. nDir = oneOf("N NORTH",caseless=True).setParseAction(replaceWith("N"))
  340. sDir = oneOf("S SOUTH",caseless=True).setParseAction(replaceWith("S"))
  341. eDir = oneOf("E EAST",caseless=True).setParseAction(replaceWith("E"))
  342. wDir = oneOf("W WEST",caseless=True).setParseAction(replaceWith("W"))
  343. moveDirection = nDir | sDir | eDir | wDir
  344. invCommand = invVerb
  345. dropCommand = dropVerb + itemRef("item")
  346. takeCommand = takeVerb + itemRef("item")
  347. useCommand = useVerb + itemRef("usedObj") + \
  348. Optional(oneOf("IN ON",caseless=True)) + \
  349. Optional(itemRef,default=None)("targetObj")
  350. openCommand = openVerb + itemRef("item")
  351. closeCommand = closeVerb + itemRef("item")
  352. moveCommand = moveVerb + moveDirection("direction")
  353. quitCommand = quitVerb
  354. lookCommand = lookVerb
  355. doorsCommand = doorsVerb
  356. helpCommand = helpVerb
  357. # attach command classes to expressions
  358. invCommand.setParseAction(InventoryCommand)
  359. dropCommand.setParseAction(DropCommand)
  360. takeCommand.setParseAction(TakeCommand)
  361. useCommand.setParseAction(UseCommand)
  362. openCommand.setParseAction(OpenCommand)
  363. closeCommand.setParseAction(CloseCommand)
  364. moveCommand.setParseAction(MoveCommand)
  365. quitCommand.setParseAction(QuitCommand)
  366. lookCommand.setParseAction(LookCommand)
  367. doorsCommand.setParseAction(DoorsCommand)
  368. helpCommand.setParseAction(HelpCommand)
  369. # define parser using all command expressions
  370. return ( invCommand |
  371. useCommand |
  372. openCommand |
  373. closeCommand |
  374. dropCommand |
  375. takeCommand |
  376. moveCommand |
  377. lookCommand |
  378. doorsCommand |
  379. helpCommand |
  380. quitCommand )("command") + LineEnd()
  381. def validateItemName(self,s,l,t):
  382. iname = " ".join(t)
  383. if iname not in Item.items:
  384. raise AppParseException(s,l,"No such item '%s'." % iname)
  385. return iname
  386. def parseCmd(self, cmdstr):
  387. try:
  388. ret = self.bnf.parseString(cmdstr)
  389. return ret
  390. except AppParseException as pe:
  391. print(pe.msg)
  392. except ParseException as pe:
  393. print(random.choice([ "Sorry, I don't understand that.",
  394. "Huh?",
  395. "Excuse me?",
  396. "???",
  397. "What?" ] ))
  398. class Player(object):
  399. def __init__(self, name):
  400. self.name = name
  401. self.gameOver = False
  402. self.inv = []
  403. def moveTo(self, rm):
  404. self.room = rm
  405. rm.enter(self)
  406. if self.gameOver:
  407. if rm.desc:
  408. rm.describe()
  409. print("Game over!")
  410. else:
  411. rm.describe()
  412. def take(self,it):
  413. if it.isDeadly:
  414. print("Aaaagh!...., the %s killed me!" % it)
  415. self.gameOver = True
  416. else:
  417. self.inv.append(it)
  418. def drop(self,it):
  419. self.inv.remove(it)
  420. if it.isFragile:
  421. it.breakItem()
  422. def createRooms( rm ):
  423. """
  424. create rooms, using multiline string showing map layout
  425. string contains symbols for the following:
  426. A-Z, a-z indicate rooms, and rooms will be stored in a dictionary by
  427. reference letter
  428. -, | symbols indicate connection between rooms
  429. <, >, ^, . symbols indicate one-way connection between rooms
  430. """
  431. # start with empty dictionary of rooms
  432. ret = {}
  433. # look for room symbols, and initialize dictionary
  434. # - exit room is always marked 'Z'
  435. for c in rm:
  436. if c in string.ascii_letters:
  437. if c != "Z":
  438. ret[c] = Room(c)
  439. else:
  440. ret[c] = Exit()
  441. # scan through input string looking for connections between rooms
  442. rows = rm.split("\n")
  443. for row,line in enumerate(rows):
  444. for col,c in enumerate(line):
  445. if c in string.ascii_letters:
  446. room = ret[c]
  447. n = None
  448. s = None
  449. e = None
  450. w = None
  451. # look in neighboring cells for connection symbols (must take
  452. # care to guard that neighboring cells exist before testing
  453. # contents)
  454. if col > 0 and line[col-1] in "<-":
  455. other = line[col-2]
  456. w = ret[other]
  457. if col < len(line)-1 and line[col+1] in "->":
  458. other = line[col+2]
  459. e = ret[other]
  460. if row > 1 and col < len(rows[row-1]) and rows[row-1][col] in '|^':
  461. other = rows[row-2][col]
  462. n = ret[other]
  463. if row < len(rows)-1 and col < len(rows[row+1]) and rows[row+1][col] in '|.':
  464. other = rows[row+2][col]
  465. s = ret[other]
  466. # set connections to neighboring rooms
  467. room.doors=[n,s,e,w]
  468. return ret
  469. # put items in rooms
  470. def putItemInRoom(i,r):
  471. if isinstance(r,str):
  472. r = rooms[r]
  473. r.addItem( Item.items[i] )
  474. def playGame(p,startRoom):
  475. # create parser
  476. parser = Parser()
  477. p.moveTo( startRoom )
  478. while not p.gameOver:
  479. cmdstr = input(">> ")
  480. cmd = parser.parseCmd(cmdstr)
  481. if cmd is not None:
  482. cmd.command( p )
  483. print()
  484. print("You ended the game with:")
  485. for i in p.inv:
  486. print(" -", aOrAn(i))
  487. #====================
  488. # start game definition
  489. roomMap = """
  490. d-Z
  491. |
  492. f-c-e
  493. . |
  494. q<b
  495. |
  496. A
  497. """
  498. rooms = createRooms( roomMap )
  499. rooms["A"].desc = "You are standing on the front porch of a wooden shack."
  500. rooms["b"].desc = "You are in a garden."
  501. rooms["c"].desc = "You are in a kitchen."
  502. rooms["d"].desc = "You are on the back porch."
  503. rooms["e"].desc = "You are in a library."
  504. rooms["f"].desc = "You are on the patio."
  505. rooms["q"].desc = "You are sinking in quicksand. You're dead..."
  506. rooms["q"].gameOver = True
  507. # define global variables for referencing rooms
  508. frontPorch = rooms["A"]
  509. garden = rooms["b"]
  510. kitchen = rooms["c"]
  511. backPorch = rooms["d"]
  512. library = rooms["e"]
  513. patio = rooms["f"]
  514. # create items
  515. itemNames = """sword.diamond.apple.flower.coin.shovel.book.mirror.telescope.gold bar""".split(".")
  516. for itemName in itemNames:
  517. Item( itemName )
  518. Item.items["apple"].isDeadly = True
  519. Item.items["mirror"].isFragile = True
  520. Item.items["coin"].isVisible = False
  521. Item.items["shovel"].usableConditionTest = ( lambda p,t: p.room is garden )
  522. def useShovel(p,subj,target):
  523. coin = Item.items["coin"]
  524. if not coin.isVisible and coin in p.room.inv:
  525. coin.isVisible = True
  526. Item.items["shovel"].useAction = useShovel
  527. Item.items["telescope"].isTakeable = False
  528. def useTelescope(p,subj,target):
  529. print("You don't see anything.")
  530. Item.items["telescope"].useAction = useTelescope
  531. OpenableItem("treasure chest", Item.items["gold bar"])
  532. Item.items["chest"] = Item.items["treasure chest"]
  533. Item.items["chest"].isTakeable = False
  534. Item.items["chest"].cantTakeMessage = "It's too heavy!"
  535. OpenableItem("mailbox")
  536. Item.items["mailbox"].isTakeable = False
  537. Item.items["mailbox"].cantTakeMessage = "It's nailed to the wall!"
  538. putItemInRoom("mailbox", frontPorch)
  539. putItemInRoom("shovel", frontPorch)
  540. putItemInRoom("coin", garden)
  541. putItemInRoom("flower", garden)
  542. putItemInRoom("apple", library)
  543. putItemInRoom("mirror", library)
  544. putItemInRoom("telescope", library)
  545. putItemInRoom("book", kitchen)
  546. putItemInRoom("diamond", backPorch)
  547. putItemInRoom("treasure chest", patio)
  548. # create player
  549. plyr = Player("Bob")
  550. plyr.take( Item.items["sword"] )
  551. # start game
  552. playGame( plyr, frontPorch )