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

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:

  1. package com.varaneckas;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5. import java.util.LinkedList;  
  6.   
  7. /** 
  8.  * {@link InputStream} implementation that allows chaining of various  
  9.  * streams for seamless sequential reading 
  10.  *  
  11.  * @author Tomas Varaneckas <tomas.varaneckas@gmail.com> 
  12.  */  
  13. public class InputStreamChain extends InputStream {  
  14.       
  15.     /** 
  16.      * Input stream chain 
  17.      */  
  18.     private final LinkedList<InputStream> streams = new LinkedList<InputStream>();  
  19.       
  20.     /** 
  21.      * Currently active stream 
  22.      */  
  23.     private InputStream current;  
  24.       
  25.     /** 
  26.      * Default constructor 
  27.      */  
  28.     public InputStreamChain() {  
  29.         //nothing to do  
  30.     }  
  31.       
  32.     /** 
  33.      * Constructor with an initial stream 
  34.      *  
  35.      * @param first Initial InputStream 
  36.      */  
  37.     public InputStreamChain(final InputStream first) {  
  38.         addInputStream(first);  
  39.     }  
  40.       
  41.     /** 
  42.      * Constructor with an array of initial streams 
  43.      *  
  44.      * @param streams Array of initial InputStreams 
  45.      */  
  46.     public InputStreamChain(final InputStream[] streams) {  
  47.         for (InputStream stream : streams) {  
  48.             addInputStream(stream);  
  49.         }  
  50.     }  
  51.   
  52.     /** 
  53.      * Vararg constructor 
  54.      *  
  55.      * @param streams initial input streams 
  56.      */  
  57.     public InputStreamChain(final InputStream ... streams) {  
  58.         for (InputStream stream : streams) {  
  59.             addInputStream(stream);  
  60.         }  
  61.     }  
  62.   
  63.     /** 
  64.      * Adds input stream to the end of chain 
  65.      *  
  66.      * @param stream InputStream to add to chain 
  67.      * @return instance of self (for fluent calls) 
  68.      */  
  69.     public InputStreamChain addInputStream(final InputStream stream) {  
  70.         streams.addLast(stream);  
  71.         if (current == null) {  
  72.             current = streams.removeFirst();  
  73.         }  
  74.         return this;  
  75.     }  
  76.   
  77.     @Override  
  78.     public int read() throws IOException {  
  79.         int bit = current.read();  
  80.         if (bit == -1 && streams.size() > 0) {  
  81.             try {  
  82.                 current.close();  
  83.             } catch (final IOException e) {  
  84.                 //replace this with a call to logging facility  
  85.                 e.printStackTrace();  
  86.             }  
  87.             current = streams.removeFirst();  
  88.             bit = read();  
  89.         }  
  90.         return bit;  
  91.     }  
  92.       
  93.     @Override  
  94.     public int available() throws IOException {  
  95.         int available = current.available();  
  96.         for (InputStream stream : streams) {  
  97.             available += stream.available();  
  98.         }  
  99.         return available;  
  100.     }  
  101.       
  102.     @Override  
  103.     public void close() throws IOException {  
  104.         current.close();  
  105.     }  
  106.       
  107.     @Override  
  108.     public boolean markSupported() {  
  109.         return current.markSupported();  
  110.     }  
  111.       
  112.     @Override  
  113.     public synchronized void mark(int i) {  
  114.         current.mark(i);  
  115.     }  
  116.       
  117.     @Override  
  118.     public synchronized void reset() throws IOException {  
  119.         current.reset();  
  120.     }  
  121.   
  122.     @Override  
  123.     public long skip(long l) throws IOException {  
  124.         return current.skip(l);  
  125.     }  
  126.       
  127. }  


Example code:
  1. InputStream chuck = new ByteArrayInputStream("Chuck ".getBytes());  
  2. InputStream norris = new ByteArrayInputStream("Norris".getBytes());  
  3. InputStream chuckNorris = new InputStreamChain()  
  4.         .addInputStream(chuck)  
  5.         .addInputStream(norris);  
  6. //will print "Chuck Norris"  
  7. System.out.println(new BufferedReader(  
  8.         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.