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 :)

2008-11-11

InputStreamChain

If you have several Java InputStreams that you want to queue up into a single InputStream object, you can use this:

package com.varaneckas;

import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;

/**
* {@link InputStream} implementation that allows chaining of various
* streams for seamless sequential reading
*
* @author Tomas Varaneckas <tomas.varaneckas@gmail.com>
*/
public class InputStreamChain extends InputStream {

/**
* Input stream chain
*/
private final LinkedList<InputStream> streams = new LinkedList<InputStream>();

/**
* Currently active stream
*/
private InputStream current;

/**
* Default constructor
*/
public InputStreamChain() {
//nothing to do
}

/**
* Constructor with an initial stream
*
* @param first Initial InputStream
*/
public InputStreamChain(final InputStream first) {
addInputStream(first);
}

/**
* Constructor with an array of initial streams
*
* @param streams Array of initial InputStreams
*/
public InputStreamChain(final InputStream[] streams) {
for (InputStream stream : streams) {
addInputStream(stream);
}
}

/**
* Vararg constructor
*
* @param streams initial input streams
*/
public InputStreamChain(final InputStream ... streams) {
for (InputStream stream : streams) {
addInputStream(stream);
}
}

/**
* Adds input stream to the end of chain
*
* @param stream InputStream to add to chain
* @return instance of self (for fluent calls)
*/
public InputStreamChain addInputStream(final InputStream stream) {
streams.addLast(stream);
if (current == null) {
current = streams.removeFirst();
}
return this;
}

@Override
public int read() throws IOException {
int bit = current.read();
if (bit == -1 && streams.size() > 0) {
try {
current.close();
} catch (final IOException e) {
//replace this with a call to logging facility
e.printStackTrace();
}
current = streams.removeFirst();
bit = read();
}
return bit;
}

@Override
public int available() throws IOException {
int available = current.available();
for (InputStream stream : streams) {
available += stream.available();
}
return available;
}

@Override
public void close() throws IOException {
current.close();
}

@Override
public boolean markSupported() {
return current.markSupported();
}

@Override
public synchronized void mark(int i) {
current.mark(i);
}

@Override
public synchronized void reset() throws IOException {
current.reset();
}

@Override
public long skip(long l) throws IOException {
return current.skip(l);
}

}


Example code:
InputStream chuck = new ByteArrayInputStream("Chuck ".getBytes());
InputStream norris = new ByteArrayInputStream("Norris".getBytes());
InputStream chuckNorris = new InputStreamChain()
.addInputStream(chuck)
.addInputStream(norris);
//will print "Chuck Norris"
System.out.println(new BufferedReader(
new InputStreamReader(chuckNorris)).readLine());


Have fun!

2008-11-06

Software design tips from the creator of C++ programming language

[1] Know what you are trying to achieve
[2] Keep in mind that software development is a human activity
[3] Proof by analogy is fraud
[4] Have specific and tangible aims
[5] Don’t try technological fixes for sociological problems
[6] Consider the longer term in design and in the treatment of people
[7] There is no lower limit to the size of programs for which it is sensible to design before starting to code
[8] Design processes to encourage feedback
[9] Don’t confuse activity for progress
[10] Don’t generalize beyond what is needed, what you have direct experience with, and what can be tested
[11] Represent concepts as classes
[12] There are properties of a system that should not be represented as a class
[13] Represent hierarchical relationships between concepts as class hierarchies
[14] Actively search for commonality in the concepts of the application and implementation and represent the resulting more general concepts as base classes
[15] Classifications in other domains are not necessarily useful classifications in an inheritance model for an application
[16] Design class hierarchies based on behaviour and invariants
[17] Consider use cases
[18] Consider using CRC cards
[19] Use existing systems as models, as inspiration, and as starting points
[20] Beware of viewgraph engineering
[21] Throw a prototype away before it becomes a burden
[22] Design for change, focusing on flexibility, extensibility, portability, and reuse
[23] Focus on component design
[24] Let each interface represent a concept at a single level of abstraction
[25] Design for stability in the face of change
[26] Make designs stable by making heavily used interfaces minimal, general, and abstract
[27] Keep it small. Don’t add features "just in case"
[28] Always consider alternative representations for a class. If no alternative representation is plausible, the class is probably not representing a clean concept
[29] Repeatedly review and refine both the design and the implementation
[30] Use the best tools available for testing and for analysing the problem, the design, and the implementation
[31] Experiment, analyse, and test as early as possible and as often as possible
[32] Don’t forget about efficiency
[33] Keep the level of formality appropriate to the scale of the project
[34] Make sure that someone is in charge of the overall design
[35] Document, market, and support reusable components
[36] Document aims and principles as well as details
[37] Provide tutorials for new developers as part of the documentation
[38] Reward and encourage reuse of designs, libraries, and classes

I found these great tips in a classic programming book: The C++ Programming Language Third Edition by Bjarne Stroustrup, the creator of C++. If you want to learn C++ or deepen your knowledge, this is The Book.