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

class MudObject:
def __init__(self, name, sight, collide = 'nothing happens', usability = 'unusable'):
self.name = name
self.sight = sight
self.collide = collide
self.usability = usability
def view(self):
return self.sight
def touch(self):
return self.collide
def use(self):
return self.usability
class MudPlayer:
def __init__(self, name):
self.inventory = {}
self.name = name
self.health = 100
def move(self, area):
return self.name + ' moves to ' + area.sight
def take(self, obj):
self.inventory[obj.name] = obj
return self.name + ' puts ' + obj.name + ' in his inventory'
def drop(self, name):
if self.inventory.has_key(name):
return self.inventory.pop(name)
def say(self, what):
return self.name + ' says: ' + what
def use(self, what):
if self.inventory.has_key(what):
return self.inventory[what].use()
else:
return 'you do not have ' + what

class MudArea:
def __init__(self, sight):
self.objects = {}
self.panorama = {}
self.sight = sight
self.inverted_directions = {'north':'south', 'south':'north', 'east':'west', 'west':'east'}
def addArea(self, direction, area):
area.panorama[self.inverted_directions[direction]] = self
self.panorama[direction] = area

def relocate(self, args):
try:
return self.panorama[args]
except KeyError:
return None
def addObject(self, name, obj):
if obj != None:
self.objects[name] = obj
return name + ' was dropped..'
def getObject(self, name):
if self.objects.has_key(name):
return self.objects.pop(name)
else:
return 'there is no ' + name + ' arround!'
def touchObject(self, name):
if self.objects.has_key(name):
return self.objects[name].touch()
else:
return 'there is no ' + name + ' arround!'
def view(self, args = 'arround'):
if (args != '' and args != 'arround'):
try:
return self.panorama[args].view()
except KeyError:
try:
return self.objects[args].view()
except KeyError:
return 'nothing.'
else:
objects = ', '.join([k for k, v in self.objects.items()])
if (objects != ''):
obsight = '. There also seems to be: ' + objects
else:
obsight = ''
return self.sight + obsight
import sys

class MudCommand:
""" welcome to mud. available commands are:
go, move, help, exit, look, touch, say, take, drop, inventory, use """
def __init__(self, char, area):
self.char = char
self.area = area

def go(self, args):
""" alias of move """
return self.move(args)

def use(self, args):
""" uses item from inventory """
return self.char.use(args)

def inventory(self, args):
""" displays inventory """
return self.char.name + ' has: ' + ', '.join(self.char.inventory)

def help(self, args):
""" gives you help on a topic"""
if args == '':
return self.__doc__
else:
try:
return getattr(self, args).__doc__
except AttributeError:
return 'help topic not found'

def exit(self, args):
""" exits game """
print 'bye bye!'
sys.exit()

def look(self, args):
""" lets you look arround """
return self.char.name + ' sees ' + self.area.view(args)

def take(self, args):
""" takes item from the ground """
try:
return self.char.take(self.area.getObject(args))
except AttributeError:
return 'you cannot take ' + args

def touch(self, args):
""" touches item from the ground """
return self.area.touchObject(args)

def drop(self, args):
""" drops item from inventory to current area """
return self.area.addObject(args, self.char.drop(args))

def move(self, args):
""" moves arround """
area = self.area.relocate(args)
if area != None:
self.area = area
return self.char.move(self.area)
else:
return 'There seems to be nothing that way.'

def say(self, args):
""" makes character talk """
return self.char.say(args)
class MudGame:
def __init__(self, char, area):
self.cmd = MudCommand(char, area)

def run(self):
while True:
command = raw_input('> ');
self.parse(command)

def parse(self, command):
comm = command.lower().split(' ')
try:
cmd = comm[0]
except IndexError:
cmd = 'help'
try:
args = comm[1:]
except IndexError:
args = []
try:
result = getattr(self.cmd, cmd)(' '.join(args).strip())
except AttributeError:
result = 'Unknown Command'
print result


And the main game script - mud.py:

from engine import *

#objects (name, description, on touch, on use)
rose = MudObject('rose', 'a red blossom with spikes', 'bites fingers!', 'wanna eat it or what?')
shit = MudObject('shit', 'a stinky one', 'ewww...', 'you are sick, you know that?')
gaidys = MudObject('bird', 'oh, a cock!', 'looks like a rainbow', 'cuckarekoo! motherfucka!?!')

#areas
woods = MudArea('deep green woods')
river = MudArea('shallow river')
hills = MudArea('orc hills')
house = MudArea('house of all gay')
meadow = MudArea('a green smelly meadow')

#attaching interactive stuff to areas
river.addObject('object', shit)
woods.addObject('flower', rose)
woods.addObject('bird', gaidys)
meadow.addObject('animal', gaidys)

#link all areas with bidirectional references
river.addArea('south', hills)
woods.addArea('north', river)
woods.addArea('west', house)
hills.addArea('east', meadow)
meadow.addArea('north', woods)

#create a player
char = MudPlayer('spajus')

#create a game with player and starting area
game = MudGame(char, woods)

#lets go!
game.run()


OK, and of course, unit tests:

import unittest
from engine import *

class TestMudObject(unittest.TestCase):
def setUp(self):
self.o = MudObject('object1', 'sight1', 'collision1', 'usage1')
self.o2 = MudObject('object2', 'sight2')

def test_view(self):
self.assertEqual(self.o.view(), 'sight1')
self.assertEqual(self.o2.view(), 'sight2')
self.assertNotEqual(self.o.view(), 'c')
self.assertNotEqual(self.o.view(), self.o2.view())

def test_touch(self):
self.assertEqual(self.o.touch(), 'collision1')
self.assertEqual(self.o2.touch(), 'nothing happens')
self.assertNotEqual(self.o.touch(), 'sight1')
self.assertNotEqual(self.o.touch(), self.o2.touch())

def test_use(self):
self.assertEqual(self.o.use(), 'usage1')
self.assertEqual(self.o2.use(), 'unusable')
self.assertNotEqual(self.o.use(), 'unsuable')
self.assertNotEqual(self.o.use(), self.o2.use())

class TestMudPlayer(unittest.TestCase):
def setUp(self):
self.p1 = MudPlayer('player1')
self.p2 = MudPlayer('player2')
self.area1 = MudArea('area1')
self.area2 = MudArea('area2')
self.o = MudObject('object1', 'sight1', 'collision1', 'usage1')
self.o2 = MudObject('object2', 'sight2')

def test_move(self):
f1 = self.p1.move
f2 = self.p2.move
a1 = self.area1
a2 = self.area2
self.assertEqual(f1(a1), 'player1 moves to area1')
self.assertEqual(f2(a1), 'player2 moves to area1')
self.assertEqual(f1(a2), 'player1 moves to area2')
self.assertEqual(f2(a2), 'player2 moves to area2')
self.assertNotEqual(f1(a1), 'player1 moves to area2')

def test_take_drop(self):
take = self.p1.take
use = self.p1.use
drop = self.p1.drop
inven = self.p1.inventory

o1 = self.o
o2 = self.o2

self.assertEqual(inven, {})
self.assertEqual(take(o1), 'player1 puts object1 in his inventory')
self.assertEqual(inven, {'object1':o1})
self.assertEqual(take(o2), 'player1 puts object2 in his inventory')
self.assertEqual(inven, {'object1':o1, 'object2':o2})

self.assertEqual(drop('object1'), o1)
#neesamo objekto dropint neina
self.assertRaises(TypeError, drop('object1'))
self.assertEqual(inven, {'object2':o2})
self.assertEqual(drop('object2'), o2)
self.assertEqual(inven, {})

def test_use(self):
p1 = self.p1
o1 = self.o
self.assertNotEqual(p1.use('object1'), 'usage1')
self.assertEqual(p1.use('object1'), 'you do not have object1')
p1.take(o1)
self.assertEqual(p1.use('object1'), 'usage1')

class TestMudArea(unittest.TestCase):
def setUp(self):
self.a1 = MudArea('area1')
self.a2 = MudArea('area2')
self.o1 = MudObject('obj1', 'sight1', 'collide1', 'use1')
self.o2 = MudObject('obj2', 'sight2')

def test_addArea(self):
#assignmentas turi buti veidrodinis
self.a1.addArea('north', self.a2)
self.assertEqual(self.a1.panorama, {'north':self.a2})
self.assertEqual(self.a2.panorama, {'south':self.a1})

def test_relocate(self):
self.a1.addArea('north', self.a2)
self.assertEqual(self.a1.relocate('north'), self.a2)
self.assertEqual(self.a2.relocate('north'), None)
self.assertEqual(self.a2.relocate('south'), self.a1)

def test_addObject(self):
self.assertEqual(self.a1.objects, {})
#dropped returninama todel, kad paprastai objectas zaidimo metu addinamas tada, kai playeris dropina ji.
#kreivai biski, bet ka padarysi :)
self.assertEqual(self.a1.addObject('something', self.o1), 'something was dropped..')
self.assertEqual(self.a1.objects, {'something':self.o1})
self.a1.addObject('other', self.o2)
self.assertEqual(self.a1.objects, {'something':self.o1, 'other':self.o2})
self.assertEqual(self.a1.addObject('something_clone', self.o1), 'something_clone was dropped..')
self.assertEqual(self.a1.objects, {'something':self.o1, 'other':self.o2, 'something_clone':self.o1})

def test_getObject(self):
self.assertEqual(self.a1.objects, {})
self.a1.addObject('something', self.o1)
self.a1.addObject('other', self.o2)
self.assertEqual(self.a1.getObject('something'), self.o1)
self.assertEqual(self.a1.objects, {'other':self.o2})
self.assertEqual(self.a1.getObject('something'), 'there is no something arround!')

def test_touchObject(self):
self.assertEqual(self.a1.objects, {})
self.a1.addObject('something', self.o1)
self.a1.addObject('other', self.o2)
self.assertEqual(self.a1.touchObject('something'), self.o1.touch())
self.assertNotEqual(self.a1.touchObject('obj2'), self.o2.touch())
self.assertEqual(self.a1.touchObject('ass'), 'there is no ass arround!')

def test_view(self):
view = self.a1.view
self.assertEqual(view(), 'area1')
#unindentified object/panorama
self.assertEqual(view('my brain'), 'nothing.')

self.a1.addObject('my brain', self.o1)
self.assertEqual(view('my brain'), self.o1.view())
#padarom dar idomiau. kadangi pridejom my brain, reikia parodyti ir tai..
self.assertNotEqual(view(), 'area1')
self.assertEqual(view(), 'area1. There also seems to be: my brain')
self.a1.addObject('duck', self.o2)
self.assertEqual(view(), 'area1. There also seems to be: my brain, duck') #', '.join(self.a1.objects)

self.assertEqual(view('north'), 'nothing.')
self.a1.addArea('north', self.a2)
self.assertEqual(view('north'), 'area2')
#kadangi priskyrem area2, turejo atsispindeti ir is ten paziurejus i priesinga north krypti (south), turi matytis area1 viewas
self.assertEqual(self.a2.view('south'), view())
#akurat.. matosi :)

class MudCommandTest(unittest.TestCase):
def setUp(self):
self.p1 = MudPlayer('player1')
self.a1 = MudArea('area1')
self.a2 = MudArea('area2')
self.o1 = MudObject('obj1', 'sight1', 'collide1', 'use1')
self.o2 = MudObject('obj2', 'sight2', 'collide2', 'use2')
self.a2.addObject('bread', self.o1)
self.a2.addObject('pig', self.o2)
self.a1.addArea('east', self.a2)
self.c = MudCommand(self.p1, self.a1)

def test_go_move(self):
#MudArea.go === MudArea.move
#test wrong way
self.assertEqual(self.c.go('somewhere'), 'There seems to be nothing that way.')
#test walk arround
self.assertEqual(self.c.go('east'), 'player1 moves to area2')
self.assertEqual(self.c.go('west'), 'player1 moves to area1')

def test_use(self):
self.assertEqual(self.c.use('bla'), 'you do not have bla')
#lets go east and take something to test using
self.c.go('east')
self.c.take('bread')
#as bread was only the looks, we know it's actually obj1, so lets use it
self.assertEqual(self.c.use('obj1'), self.o1.use())

def test_inventory(self):
self.c.go('east')
self.c.take('bread')
self.assertEqual(self.c.inventory(None), 'player1 has: obj1')

def test_help(self):
self.assertEqual(self.c.help(''), self.c.__doc__)
self.assertEqual(self.c.help('move'), self.c.move.__doc__)
self.assertEqual(self.c.help('blabla'), 'help topic not found')

def test_look(self):
self.assertEqual(self.c.look(''), 'player1 sees ' + self.a1.view())
self.assertEqual(self.c.look('at my balls'), 'player1 sees nothing.')
self.assertEqual(self.c.look('east'), 'player1 sees ' + self.a2.view())

def test_take(self):
self.c.go('east')
self.assertEqual(self.c.take('bread'), 'player1 puts obj1 in his inventory')
self.assertEqual(self.p1.inventory, {'obj1':self.o1})
#already taken!
self.assertEqual(self.c.take('bread'), 'you cannot take bread')

def test_touch(self): #perv test.. :)
self.assertEqual(self.c.touch('self'), 'there is no self arround!')
self.assertNotEqual(self.c.touch('bread'), self.o1.touch())
self.c.go('east')
#kad paliesti reik pirma nueiti
self.assertEqual(self.c.touch('bread'), self.o1.touch())

def test_drop(self):
self.assertEqual(self.c.drop('smelly thing'), None)
self.c.go('east')
self.c.take('bread')
self.c.go('west')
self.assertEqual(self.c.drop('obj1'), 'obj1 was dropped..')
self.assertEqual(self.a1.objects, {'obj1':self.o1})

def test_say(self):
self.assertEqual(self.c.say('i love this game'), 'player1 says: i love this game')
self.assertNotEqual(self.c.say('python sucks'), 'player1 says: that\'s true!')

if __name__ == '__main__':
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!