2008-11-25

Python MUD Game Example

I've found a small MUD-like single player game that I did with Python several years ago. Thought it would be nice to share the source. So here it is.

First of all, a live demo (type help. gone if my internet connection or home server is down.)

server down


And a "screenshot" (sorry for my bad sense of humor):


> help
welcome to mud. available commands are:
go, move, help, exit, look, say, take, drop, inventory, use
>
Unknown Command
> look
spajus sees deep green woods. There also seems to be: flower, bird
> touch bird
looks like a rainbow
> use bird
you do not have bird
> take bird
spajus puts bird in his inventory
> use bird
cuckarekoo! motherfucka!?!
> drop bird
bird was dropped..
> kill bird
Unknown Command
> look north
spajus sees shallow river. There also seems to be: object
> go north
spajus moves to shallow river
> look object
spajus sees a stinky one
> touch object
ewww...
> take object
spajus puts shit in his inventory
> use shit
you are sick, you know that?


Now, the engine.py
  1. class MudObject:  
  2.  def __init__(self, name, sight, collide = 'nothing happens', usability = 'unusable'):  
  3.    self.name = name  
  4.    self.sight = sight  
  5.    self.collide = collide  
  6.    self.usability = usability  
  7.  def view(self):  
  8.    return self.sight  
  9.  def touch(self):  
  10.    return self.collide  
  11.  def use(self):  
  12.    return self.usability  
  13. class MudPlayer:  
  14.  def __init__(self, name):  
  15.    self.inventory = {}  
  16.    self.name = name  
  17.    self.health = 100  
  18.  def move(self, area):  
  19.    return self.name + ' moves to ' + area.sight  
  20.  def take(self, obj):  
  21.    self.inventory[obj.name] = obj  
  22.    return self.name + ' puts ' + obj.name + ' in his inventory'  
  23.  def drop(self, name):  
  24.    if self.inventory.has_key(name):  
  25.      return self.inventory.pop(name)  
  26.  def say(self, what):  
  27.    return self.name + ' says: ' + what  
  28.  def use(self, what):  
  29.    if self.inventory.has_key(what):  
  30.      return self.inventory[what].use()  
  31.    else:  
  32.      return 'you do not have ' + what  
  33.   
  34. class MudArea:  
  35.  def __init__(self, sight):  
  36.    self.objects = {}  
  37.    self.panorama = {}  
  38.    self.sight = sight  
  39.    self.inverted_directions = {'north':'south''south':'north''east':'west''west':'east'}  
  40.  def addArea(self, direction, area):  
  41.    area.panorama[self.inverted_directions[direction]] = self  
  42.    self.panorama[direction] = area  
  43.     
  44.  def relocate(self, args):     
  45.    try:  
  46.      return self.panorama[args]  
  47.    except KeyError:  
  48.      return None  
  49.  def addObject(self, name, obj):  
  50.    if obj != None:  
  51.      self.objects[name] = obj  
  52.      return name + ' was dropped..'  
  53.  def getObject(self, name):  
  54.    if self.objects.has_key(name):  
  55.      return self.objects.pop(name)  
  56.    else:  
  57.      return 'there is no ' + name + ' arround!'  
  58.  def touchObject(self, name):  
  59.    if self.objects.has_key(name):  
  60.      return self.objects[name].touch()  
  61.    else:  
  62.      return 'there is no ' + name + ' arround!'  
  63.  def view(self, args = 'arround'):  
  64.    if (args != '' and args != 'arround'):  
  65.      try:  
  66.        return self.panorama[args].view()  
  67.      except KeyError:  
  68.        try:  
  69.          return self.objects[args].view()  
  70.        except KeyError:  
  71.          return 'nothing.'  
  72.    else:  
  73.      objects = ', '.join([k for k, v in self.objects.items()])  
  74.      if (objects != ''):         
  75.        obsight = '. There also seems to be: ' + objects  
  76.      else:  
  77.        obsight = ''  
  78.      return self.sight + obsight  
  79. import sys  
  80.   
  81. class MudCommand:  
  82.  """ welcome to mud. available commands are: 
  83.  go, move, help, exit, look, touch, say, take, drop, inventory, use """  
  84.  def __init__(self, char, area):  
  85.    self.char = char  
  86.    self.area = area  
  87.   
  88.  def go(self, args):  
  89.    """ alias of move """  
  90.    return self.move(args)  
  91.   
  92.  def use(self, args):  
  93.    """ uses item from inventory """  
  94.    return self.char.use(args)  
  95.   
  96.  def inventory(self, args):  
  97.    """ displays inventory """  
  98.    return self.char.name + ' has: ' + ', '.join(self.char.inventory)  
  99.   
  100.  def help(self, args):  
  101.    """ gives you help on a topic"""       
  102.    if args == '':  
  103.      return self.__doc__  
  104.    else:  
  105.      try:  
  106.        return getattr(self, args).__doc__  
  107.      except AttributeError:  
  108.        return 'help topic not found'  
  109.   
  110.  def exit(self, args):  
  111.    """ exits game """   
  112.    print 'bye bye!'  
  113.    sys.exit()  
  114.     
  115.  def look(self, args):  
  116.    """ lets you look arround """  
  117.    return self.char.name + ' sees ' + self.area.view(args)  
  118.   
  119.  def take(self, args):  
  120.    """ takes item from the ground """  
  121.    try:  
  122.      return self.char.take(self.area.getObject(args))  
  123.    except AttributeError:  
  124.      return 'you cannot take ' + args  
  125.     
  126.  def touch(self, args):  
  127.    """ touches item from the ground """  
  128.    return self.area.touchObject(args)  
  129.   
  130.  def drop(self, args):  
  131.    """ drops item from inventory to current area """  
  132.    return self.area.addObject(args, self.char.drop(args))  
  133.   
  134.  def move(self, args):  
  135.    """ moves arround """   
  136.    area = self.area.relocate(args)  
  137.    if area != None:  
  138.      self.area = area  
  139.      return self.char.move(self.area)  
  140.    else:  
  141.      return 'There seems to be nothing that way.'  
  142.     
  143.  def say(self, args):  
  144.    """ makes character talk """  
  145.    return self.char.say(args)       
  146. class MudGame:  
  147.  def __init__(self, char, area):  
  148.    self.cmd = MudCommand(char, area)  
  149.   
  150.  def run(self):  
  151.    while True:  
  152.      command = raw_input('> ');  
  153.      self.parse(command)  
  154.     
  155.  def parse(self, command):  
  156.    comm = command.lower().split(' ')  
  157.    try:  
  158.      cmd = comm[0]  
  159.    except IndexError:  
  160.      cmd = 'help'  
  161.    try:  
  162.      args = comm[1:]  
  163.    except IndexError:  
  164.      args = []  
  165.    try:  
  166.      result = getattr(self.cmd, cmd)(' '.join(args).strip())  
  167.    except AttributeError:  
  168.      result = 'Unknown Command'  
  169.    print result  


And the main game script - mud.py:
  1. from engine import *  
  2.   
  3. #objects (name, description, on touch, on use)  
  4. rose = MudObject('rose''a red blossom with spikes''bites fingers!''wanna eat it or what?')  
  5. shit = MudObject('shit''a stinky one''ewww...''you are sick, you know that?')  
  6. gaidys = MudObject('bird''oh, a cock!''looks like a rainbow''cuckarekoo! motherfucka!?!')  
  7.   
  8. #areas  
  9. woods = MudArea('deep green woods')  
  10. river = MudArea('shallow river')  
  11. hills = MudArea('orc hills')  
  12. house = MudArea('house of all gay')  
  13. meadow = MudArea('a green smelly meadow')  
  14.   
  15. #attaching interactive stuff to areas  
  16. river.addObject('object', shit)  
  17. woods.addObject('flower', rose)  
  18. woods.addObject('bird', gaidys)  
  19. meadow.addObject('animal', gaidys)  
  20.   
  21. #link all areas with bidirectional references  
  22. river.addArea('south', hills)  
  23. woods.addArea('north', river)  
  24. woods.addArea('west', house)  
  25. hills.addArea('east', meadow)  
  26. meadow.addArea('north', woods)  
  27.   
  28. #create a player  
  29. char = MudPlayer('spajus')  
  30.   
  31. #create a game with player and starting area  
  32. game = MudGame(char, woods)  
  33.   
  34. #lets go!  
  35. game.run()  


OK, and of course, unit tests:
  1. import unittest  
  2. from engine import *  
  3.   
  4. class TestMudObject(unittest.TestCase):  
  5.  def setUp(self):  
  6.    self.o = MudObject('object1''sight1''collision1''usage1')  
  7.    self.o2 = MudObject('object2''sight2')  
  8.     
  9.  def test_view(self):  
  10.    self.assertEqual(self.o.view(), 'sight1')  
  11.    self.assertEqual(self.o2.view(), 'sight2')  
  12.    self.assertNotEqual(self.o.view(), 'c')  
  13.    self.assertNotEqual(self.o.view(), self.o2.view())  
  14.     
  15.  def test_touch(self):  
  16.    self.assertEqual(self.o.touch(), 'collision1')  
  17.    self.assertEqual(self.o2.touch(), 'nothing happens')  
  18.    self.assertNotEqual(self.o.touch(), 'sight1')  
  19.    self.assertNotEqual(self.o.touch(), self.o2.touch())  
  20.     
  21.  def test_use(self):  
  22.    self.assertEqual(self.o.use(), 'usage1')  
  23.    self.assertEqual(self.o2.use(), 'unusable')  
  24.    self.assertNotEqual(self.o.use(), 'unsuable')  
  25.    self.assertNotEqual(self.o.use(), self.o2.use())  
  26.     
  27. class TestMudPlayer(unittest.TestCase):  
  28.  def setUp(self):  
  29.    self.p1 = MudPlayer('player1')  
  30.    self.p2 = MudPlayer('player2')  
  31.    self.area1 = MudArea('area1')  
  32.    self.area2 = MudArea('area2')  
  33.    self.o = MudObject('object1''sight1''collision1''usage1')  
  34.    self.o2 = MudObject('object2''sight2')  
  35.     
  36.  def test_move(self):  
  37.    f1 = self.p1.move  
  38.    f2 = self.p2.move  
  39.    a1 = self.area1  
  40.    a2 = self.area2  
  41.    self.assertEqual(f1(a1), 'player1 moves to area1')  
  42.    self.assertEqual(f2(a1), 'player2 moves to area1')  
  43.    self.assertEqual(f1(a2), 'player1 moves to area2')  
  44.    self.assertEqual(f2(a2), 'player2 moves to area2')  
  45.    self.assertNotEqual(f1(a1), 'player1 moves to area2')  
  46.   
  47.  def test_take_drop(self):  
  48.    take = self.p1.take  
  49.    use = self.p1.use  
  50.    drop = self.p1.drop  
  51.    inven = self.p1.inventory  
  52.     
  53.    o1 = self.o  
  54.    o2 = self.o2  
  55.     
  56.    self.assertEqual(inven, {})  
  57.    self.assertEqual(take(o1), 'player1 puts object1 in his inventory')  
  58.    self.assertEqual(inven, {'object1':o1})  
  59.    self.assertEqual(take(o2), 'player1 puts object2 in his inventory')  
  60.    self.assertEqual(inven, {'object1':o1, 'object2':o2})  
  61.     
  62.    self.assertEqual(drop('object1'), o1)  
  63.    #neesamo objekto dropint neina  
  64.    self.assertRaises(TypeError, drop('object1'))  
  65.    self.assertEqual(inven, {'object2':o2})  
  66.    self.assertEqual(drop('object2'), o2)  
  67.    self.assertEqual(inven, {})  
  68.     
  69.  def test_use(self):  
  70.    p1 = self.p1  
  71.    o1 = self.o  
  72.    self.assertNotEqual(p1.use('object1'), 'usage1')  
  73.    self.assertEqual(p1.use('object1'), 'you do not have object1')  
  74.    p1.take(o1)  
  75.    self.assertEqual(p1.use('object1'), 'usage1')  
  76.     
  77. class TestMudArea(unittest.TestCase):  
  78.  def setUp(self):  
  79.    self.a1 = MudArea('area1')  
  80.    self.a2 = MudArea('area2')  
  81.    self.o1 = MudObject('obj1''sight1''collide1''use1')  
  82.    self.o2 = MudObject('obj2''sight2')  
  83.     
  84.  def test_addArea(self):  
  85.    #assignmentas turi buti veidrodinis  
  86.    self.a1.addArea('north'self.a2)  
  87.    self.assertEqual(self.a1.panorama, {'north':self.a2})  
  88.    self.assertEqual(self.a2.panorama, {'south':self.a1})  
  89.     
  90.  def test_relocate(self):  
  91.    self.a1.addArea('north'self.a2)  
  92.    self.assertEqual(self.a1.relocate('north'), self.a2)  
  93.    self.assertEqual(self.a2.relocate('north'), None)  
  94.    self.assertEqual(self.a2.relocate('south'), self.a1)  
  95.   
  96.  def test_addObject(self):  
  97.    self.assertEqual(self.a1.objects, {})  
  98.    #dropped returninama todel, kad paprastai objectas zaidimo metu addinamas tada, kai playeris dropina ji.  
  99.    #kreivai biski, bet ka padarysi :)  
  100.    self.assertEqual(self.a1.addObject('something'self.o1), 'something was dropped..')  
  101.    self.assertEqual(self.a1.objects, {'something':self.o1})  
  102.    self.a1.addObject('other'self.o2)  
  103.    self.assertEqual(self.a1.objects, {'something':self.o1, 'other':self.o2})  
  104.    self.assertEqual(self.a1.addObject('something_clone'self.o1), 'something_clone was dropped..')  
  105.    self.assertEqual(self.a1.objects, {'something':self.o1, 'other':self.o2, 'something_clone':self.o1})  
  106.     
  107.  def test_getObject(self):  
  108.    self.assertEqual(self.a1.objects, {})  
  109.    self.a1.addObject('something'self.o1)  
  110.    self.a1.addObject('other'self.o2)  
  111.    self.assertEqual(self.a1.getObject('something'), self.o1)  
  112.    self.assertEqual(self.a1.objects, {'other':self.o2})  
  113.    self.assertEqual(self.a1.getObject('something'), 'there is no something arround!')  
  114.     
  115.  def test_touchObject(self):  
  116.    self.assertEqual(self.a1.objects, {})  
  117.    self.a1.addObject('something'self.o1)  
  118.    self.a1.addObject('other'self.o2)  
  119.    self.assertEqual(self.a1.touchObject('something'), self.o1.touch())  
  120.    self.assertNotEqual(self.a1.touchObject('obj2'), self.o2.touch())  
  121.    self.assertEqual(self.a1.touchObject('ass'), 'there is no ass arround!')  
  122.     
  123.  def test_view(self):  
  124.    view = self.a1.view  
  125.    self.assertEqual(view(), 'area1')  
  126.    #unindentified object/panorama  
  127.    self.assertEqual(view('my brain'), 'nothing.')  
  128.     
  129.    self.a1.addObject('my brain'self.o1)  
  130.    self.assertEqual(view('my brain'), self.o1.view())  
  131.    #padarom dar idomiau. kadangi pridejom my brain, reikia parodyti ir tai..  
  132.    self.assertNotEqual(view(), 'area1')  
  133.    self.assertEqual(view(), 'area1. There also seems to be: my brain')  
  134.    self.a1.addObject('duck'self.o2)  
  135.    self.assertEqual(view(), 'area1. There also seems to be: my brain, duck'#', '.join(self.a1.objects)  
  136.     
  137.    self.assertEqual(view('north'), 'nothing.')  
  138.    self.a1.addArea('north'self.a2)  
  139.    self.assertEqual(view('north'), 'area2')  
  140.    #kadangi priskyrem area2, turejo atsispindeti ir is ten paziurejus i priesinga north krypti (south), turi matytis area1 viewas  
  141.    self.assertEqual(self.a2.view('south'), view())  
  142.    #akurat.. matosi :)  
  143.   
  144. class MudCommandTest(unittest.TestCase):  
  145.  def setUp(self):  
  146.    self.p1 = MudPlayer('player1')  
  147.    self.a1 = MudArea('area1')  
  148.    self.a2 = MudArea('area2')  
  149.    self.o1 = MudObject('obj1''sight1''collide1''use1')  
  150.    self.o2 = MudObject('obj2''sight2''collide2''use2')  
  151.    self.a2.addObject('bread'self.o1)  
  152.    self.a2.addObject('pig'self.o2)  
  153.    self.a1.addArea('east'self.a2)     
  154.    self.c = MudCommand(self.p1, self.a1)  
  155.   
  156.  def test_go_move(self):  
  157.    #MudArea.go === MudArea.move  
  158.    #test wrong way  
  159.    self.assertEqual(self.c.go('somewhere'), 'There seems to be nothing that way.')  
  160.    #test walk arround  
  161.    self.assertEqual(self.c.go('east'), 'player1 moves to area2')  
  162.    self.assertEqual(self.c.go('west'), 'player1 moves to area1')  
  163.     
  164.  def test_use(self):  
  165.    self.assertEqual(self.c.use('bla'), 'you do not have bla')  
  166.    #lets go east and take something to test using  
  167.    self.c.go('east')  
  168.    self.c.take('bread')  
  169.    #as bread was only the looks, we know it's actually obj1, so lets use it  
  170.    self.assertEqual(self.c.use('obj1'), self.o1.use())  
  171.     
  172.  def test_inventory(self):  
  173.    self.c.go('east')  
  174.    self.c.take('bread')  
  175.    self.assertEqual(self.c.inventory(None), 'player1 has: obj1')  
  176.     
  177.  def test_help(self):  
  178.    self.assertEqual(self.c.help(''), self.c.__doc__)  
  179.    self.assertEqual(self.c.help('move'), self.c.move.__doc__)  
  180.    self.assertEqual(self.c.help('blabla'), 'help topic not found')  
  181.   
  182.  def test_look(self):  
  183.    self.assertEqual(self.c.look(''), 'player1 sees ' + self.a1.view())  
  184.    self.assertEqual(self.c.look('at my balls'), 'player1 sees nothing.')  
  185.    self.assertEqual(self.c.look('east'), 'player1 sees ' + self.a2.view())  
  186.     
  187.  def test_take(self):  
  188.    self.c.go('east')  
  189.    self.assertEqual(self.c.take('bread'), 'player1 puts obj1 in his inventory')  
  190.    self.assertEqual(self.p1.inventory, {'obj1':self.o1})  
  191.    #already taken!  
  192.    self.assertEqual(self.c.take('bread'), 'you cannot take bread')  
  193.     
  194.  def test_touch(self): #perv test.. :)  
  195.    self.assertEqual(self.c.touch('self'), 'there is no self arround!')  
  196.    self.assertNotEqual(self.c.touch('bread'), self.o1.touch())  
  197.    self.c.go('east')  
  198.    #kad paliesti reik pirma nueiti  
  199.    self.assertEqual(self.c.touch('bread'), self.o1.touch())  
  200.     
  201.  def test_drop(self):  
  202.    self.assertEqual(self.c.drop('smelly thing'), None)  
  203.    self.c.go('east')  
  204.    self.c.take('bread')  
  205.    self.c.go('west')  
  206.    self.assertEqual(self.c.drop('obj1'), 'obj1 was dropped..')  
  207.    self.assertEqual(self.a1.objects, {'obj1':self.o1})  
  208.     
  209.  def test_say(self):  
  210.    self.assertEqual(self.c.say('i love this game'), 'player1 says: i love this game')  
  211.    self.assertNotEqual(self.c.say('python sucks'), 'player1 says: that\'s true!')  
  212.     
  213. if __name__ == '__main__':  
  214.    unittest.main()     


To run the game:
python mud.py


To run the tests:
python testengine.py


Damn, I really miss such coding activities :)

8 comments:

  1. thats pretty neat.

    ReplyDelete
  2. This is very cool! I'm trying to get my brain around Python and examples like this help me out!

    ReplyDelete
  3. Pretty well coded. Thanks for sharing this with us :).

    ReplyDelete
  4. "small MUD-like single player game"

    I think you missed the whole point with the M in MUD :)

    ReplyDelete
  5. Hey, thanks for sharing this.

    It's got me started in the right direction for classifying a mud. Good organization!

    ReplyDelete
  6. Hi I'm Kan:

    i follow the code, but error shows.
    the program close automatic. Please help.

    Thanks

    ReplyDelete
  7. I think that's why he said "MUD-like"... because it isn't a MUD. What was he supposed to say? UD game? :)

    Eduard K

    ReplyDelete

Spam comments (i.e. ones that contain links to web development services) will be reported along with user profiles!

Note: only a member of this blog may post a comment.