PDA

View Full Version : An Even Worse Script


darelf
08-05-2002, 01:20 PM
Playing with asyncore/asynchat because of that "project" for the classroom thing of kmj's ... I had previously put socket and select together myself, but when I looked at asyncore, it had done everything for me... Anyway, here's a really bad, and horribly useless script, HOWEVER, critique on style of programming/algorithms/etc. and any suggestions are more than welcome.... in fact, it's why I'm posting it...
and here's a few more ellipses to get me through this post....
.....
.... ..... .. .. .....

import socket
import asyncore
import asynchat
import string

"""
A simple mud server. Actually not much of anything, since
I'm just getting it to communicate over the network and handle
multiple connections. And I haven't really put much thought
into how to handle the mud objects, I'm just winging it.

Contact: Darel Finkbeiner <finkbeid@bellsouth.net>
"""

el = '\r\n'

#room classes
class room:
def __init__(self):
self.name = ""
self.sdesc = ''
self.ldesc = ''
self.objects = []
self.exits = {}

def tick(self):
#This is what to do as time passes....
#Hopefully the main loop will eventually count
#time and only call tick() when needed
pass


r = room()
r.name = "First Room"
r.sdesc = "Short Description"
r.ldesc = "Long description of the room over several\nlines. Neat, huh?"
r.exits = { 'east': 2 }
world = { 1: r }
r = room()
r.name = "Second Room"
r.sdesc = "Second Room Ever"
r.ldesc = "This is the second room ever created in this mud.\nCan you feel it?"
r.exits = { 'west': 1 }
world[2] = r

print world

#Networking classes...
class mudserv(asyncore.dispatcher):
def __init__(self, port):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind ( ('', port) )
self.listen(2)

def handle_accept(self):
mudpc (self.accept())

def writable(self):
#for i in world.keys():
# world[i].tick()

return 1

class mudcon(asynchat.async_chat):
def __init__(self, (conn, addr)):
asynchat.async_chat.__init__(self, conn)
self.set_terminator(el)
self.buffer = ''

def collect_incoming_data(self, data):
self.buffer = self.buffer + data

def found_terminator(self):
data = self.buffer
self.buffer = ''
if data == "exit-server":
raise asyncore.ExitNow
if data == "quit":
self.handle_close()
self.parseCommand( data )
self.push( "> " )

def handle_close(self):
print 'Closing'
self.close()

def parseCommand(self, data):
pass

class mudpc(mudcon):
# commands is a list of commands available to the player
def __init__(self, conn):
mudcon.__init__(self, conn)
world[1].objects.append(self)
self.location = 1
self.commands = [ 'look', 'walk' ]

def changeLoc(self, r):
self.location = r
self.parseCommand( 'look' )

def parseCommand(self, data):
if not data: return
d = string.split( data )
c = string.lower(d[0])
d = d[1:]
#Here we are only comparing to the full name of the
#command... need to parse 'alias' as well (i.e. 'l' for 'look')
if c in galias:
a = string.split(galias[c])
c = a[0]
d = a[1:] + d
if c in clist:
if c not in self.commands:
x = "You cannot '" + c + "'!!" + el
self.push(x)
else:
com = clist[c]
com.doCommand( (self, d) )
else:
x = "I don't know how to '" + c + "'." + el
self.push(x)

#command classes....
class command:
def __init__(self):
self.id = ''

def doCommand(self, args):
args[0].push("Nothing to see here..." + el)

class cLook(command):
def __init__(self):
command.__init__(self)

def doCommand(self, args):
pc = args[0]
r = world[pc.location]
pc.push( r.name + el )
for i in string.split(r.ldesc, '\n'):
pc.push( i + el )

class cWalk(command):
def __init__(self):
command.__init__(self)

def doCommand(self, args):
pc, a = args
d = a[0]
r = world[pc.location]
if d in r.exits:
pc.changeLoc(r.exits[d])
else:
pc.push("Invalid direction." + el)

#An example of a command that no-one has, in order to get the 'you cannot'
#message. It should read "You cannot 'suck'!!"...
class cSuck(command):
def __init__(self):
command.__init__(self)

def doCommand(self, args):
pc, a = args
pc.push("Wow, you suck!" + el)

# global command and alias hashes...
clist = { 'look': cLook(), 'walk': cWalk(), 'suck': cSuck() }
galias = { 'w': 'walk west', 'e': 'walk east', 'n': 'walk north',
's': 'walk south' }

if __name__ == '__main__':
import sys

port = 4000
if len(sys.argv) < 2:
print 'Using default port 4000...'
else:
port = int(sys.argv[1])
print 'Using port', port

ms = mudserv( port )

try:
asyncore.loop(5)

except asyncore.ExitNow:
print "Stopping Server...."



And so, there it is... bring out the guns/flamethrowers.....
:wstupid: (hehe... new smilies)

Strike
08-05-2002, 02:14 PM
Well, logic-wise, looks okay to me. Just one thing that kinda bugs me is how sometimes you put spaces around the arg list of a function and sometimes you don't. I never do.

darelf
08-05-2002, 02:35 PM
Originally posted by Strike
Well, logic-wise, looks okay to me. Just one thing that kinda bugs me is how sometimes you put spaces around the arg list of a function and sometimes you don't. I never do.

Heh, you're right.... I don't think I had actually noticed that before...
I think it's a visual thing with me, for some reason, some of the arguments "look" like they need a little space. Though I probably should just omit the space since I only do that occasionally (sp?), and for reasons that only my subconcious understands.

I assume, eventually, I'll move code to different files... i.e. put the room code in separate files, the commands in separate files, etc. to make it easier to add/maintain. I've noticed that trend among the other muds I've seen (LP, Diku/Rom), and it seems to work well as a way of organizing... that is, having a separate file for each room that is separately edited.

I've also thought about "dynamic loading" of rooms. So that you could insert rooms/objects into a running mud and have them instantly work. Just have a "source <file>" command that loads up a python script, redefining the room/adding a new one.

jemfinch
08-05-2002, 06:54 PM
You should read this (http://www.python.org/peps/pep-0008.html). It'll make using your Python code with others' Python code much simpler. It'll also make it easier for people who do follow those style guidelines to understand your code. As a few examples of violations I can see, you're using hard tab indentation (you should 4-spaces indentation) and you're naming your classes in lowercase.

You use a lot of really short variable names that don't seem to follow any convention. You'll do better to pick a convention (for instance, for epehemeral string variables you'll call them "s", ephemeral int variables are "i", and so on...) or pick longer, more descriptive names for your variables. Those one-letter variable names really make it hard to read your code (relative to other Python code; not, say, relative to Perl :))

You should never use + to concatenate several strings (as you do in parseCommand) -- you should always use formatting. + has to copy its first argument, which means in order to, say, have the expression "w + x + y + z", you'll copy w thrice, x twice, and y once. Instead, do "%s%s%s%s" % (w, x, y, z), becuase then you'll only copy each string once.

I believe there's a module somewhere out there that handles unambiguous abbreviation, so you don't have to worry about the galias stuff.

clist is poorly named, since it's not a list, it's a dictionary.

From what I could understand, "mudpc" should be contained in "mudcon". It shouldn't inherit from mudcon. A mudcon has-a mudpc, but a mudpc is-not-a mudcon.

Instead of setting the attributes of a room separately, why don't you write an __init__ function that takes those attributes as argments? That'll work much better, because then you can just "world.insertRoom(name, Room(name=..., exits={...}, ...))".

You'd probably do better to make "world" a class that kept a dictionary of roomNames -> rooms, and then, instead of having a room's exit dictionary point to integer offsets in the world list, it can instead just be an exit to a named room. Then your room's exits aren't dependent on the other in which they're inserted into the world.

Of course, I'm not claiming to follow all these rules perfectly all the time either, but you asked for comments, so here are mine :)

Jeremy

darelf
08-05-2002, 07:10 PM
This was extremely helpful....

Originally posted by jemfinch
You should read this (http://www.python.org/peps/pep-0008.html). It'll make using your Python code with others' Python code much simpler. It'll also make it easier for people who do follow those style guidelines to understand your code. As a few examples of violations I can see, you're using hard tab indentation (you should 4-spaces indentation) and you're naming your classes in lowercase.


Is that simply a convention? (I've been using emacs on Linux and vim on windows, and both handle the tab key differently... I should probably just stick with one)


You use a lot of really short variable names that don't seem to follow any convention. You'll do better to pick a convention (for instance, for epehemeral string variables you'll call them "s", ephemeral int variables are "i", and so on...) or pick longer, more descriptive names for your variables. Those one-letter variable names really make it hard to read your code (relative to other Python code; not, say, relative to Perl :))


I **almost** use a convention for transient variables. I probably should just stick to what I use and keep away from those otherwise.... although, that may never happen. :)


You should never use + to concatenate several strings (as you do in parseCommand) -- you should always use formatting. + has to copy its first argument, which means in order to, say, have the expression "w + x + y + z", you'll copy w thrice, x twice, and y once. Instead, do "%s%s%s%s" % (w, x, y, z), becuase then you'll only copy each string once.


I was completely unaware of any speed considerations with '+'... thanks, I'll have to start using formatting....


I believe there's a module somewhere out there that handles unambiguous abbreviation, so you don't have to worry about the galias stuff.


If there is, I'm currently unaware of it. (I'm currently unaware of a lot of things, though) It would really help out and I had previously done a cursory look for such a feature.


clist is poorly named, since it's not a list, it's a dictionary.


That's a good suggestion, though I never name ANY var as *dict... it just ain't right.... probably switch to cmap....


From what I could understand, "mudpc" should be contained in "mudcon". It shouldn't inherit from mudcon. A mudcon has-a mudpc, but a mudpc is-not-a mudcon.


That is still being debated in my head. I thought about that when I started, but decided to go with what I have. My reasoning is being able to push() on the mudpc object itself....


Instead of setting the attributes of a room separately, why don't you write an __init__ function that takes those attributes as argments? That'll work much better, because then you can just "world.insertRoom(name, Room(name=..., exits={...}, ...))".

You'd probably do better to make "world" a class that kept a dictionary of roomNames -> rooms, and then, instead of having a room's exit dictionary point to integer offsets in the world list, it can instead just be an exit to a named room. Then your room's exits aren't dependent on the other in which they're inserted into the world.


These two are high priority suggestions for me to implement. These are excellent, and go directly to how the program is organized and will work in the future....


Jeremy

Thanks for the input, you are da man....

Benny
08-06-2002, 04:10 AM
Darelf, if your interested in developing this further - into a bigger project. Then I'd sure like to help out on it. I've been wanting to write a telnet mud/talker in python for ages.........

So yeh, if your looking for a second coder or even just someone to help with testing. Drop me a line.

Cheers
Ben

jemfinch
08-06-2002, 04:38 AM
If you're interested in making it a bigger project for anything other than the experience of coding it yourself, then you should check out twisted.reality < http://www.twistedmatrix.com/ > and not reinvent the wheel :)

Then, if you just want the experience of coding it yourself, feel free to reinvent the wheel :) That's the only way the wheel gets better. Aside from that, though, check out Twisted anyway -- it provides a lot of infrastructure that you might find useful.

Jeremy

darelf
08-06-2002, 09:13 AM
Benny.... I have no problem getting some help. I had wanted to code a mud about 10 years ago when all I knew was C. I only stumbled on this thrill again because of the 'classroom' project. (And that project is moving slowly... still mostly in the planning stages). Feel free to email me.

Jem.... I appreciate the suggestion. I've looked at Twisted, but it doesn't install cleanly on Windows (seems to work great on my Linux machine at home, though). And it isn't immediately obvious, even though Python is (and it's why I love Python). When I want to do something in Python, it just seems natural. With Twisted, it seems a little too complicated for the simple things that it does.
(Of course, if the classroom project decides to use it, then I'll just have to sit down and work my way through it.... )

Benny
08-06-2002, 09:36 AM
Originally posted by darelf
Benny.... I have no problem getting some help. I had wanted to code a mud about 10 years ago when all I knew was C. I only stumbled on this thrill again because of the 'classroom' project. (And that project is moving slowly... still mostly in the planning stages). Feel free to email me.


Awesome, shall have to drop you a line then.

kmj
08-06-2002, 10:27 AM
darelf: with vim, you can handle tabs any way you want to. If you want your tabs to be spaces, just ':set expandtabs', ':set softtabstop=X'.. where X is the number of spaces you want to insert.

darelf
08-06-2002, 03:16 PM
KMJ: That's what I love about this forum. There's more info here that is useful to me than anywhere else....

GnuVince
08-06-2002, 03:28 PM
Does anyone find it annoying that the style guide of Python talks so much about Emacs? You just want to do everything differently to piss the Emacs people, so that they use your python.vimrc :)

jemfinch
08-06-2002, 04:07 PM
Originally posted by darelf
Jem.... I appreciate the suggestion. I've looked at Twisted, but it doesn't install cleanly on Windows (seems to work great on my Linux machine at home, though).


I run XP professional, and it installed cleanly for me...


With Twisted, it seems a little too complicated for the simple things that it does.


Twisted doesn't do just simple things. In fact, compared to, say, asyncore, it does things at least about tenfold more complicated.

Jeremy

jemfinch
08-06-2002, 04:20 PM
Originally posted by GnuVince
Does anyone find it annoying that the style guide of Python talks so much about Emacs?


No, not at all. Emacs facilitates Python programming much better than Vim. That's a simple fact. If the style guide of Python talked about Vim, I'd be upset, because while Vim is a great editor, it simply doesn't provide all the tools and conveniences that Emacs offers for programming in Python. To promote Vim over Emacs would be to promote an inferior product, and that's just not the Pythonic Way.

Jeremy

kmj
08-06-2002, 05:11 PM
darelf, have you set up a custom twisted server? it's actually quite simple http://twistedmatrix.com/documents/howto/servers

For me it was just a matter of making sure the twisted code was in my PYTHONPATH and importing the proper modules. I got the QOTD server running quite easily.

GnuVince
08-06-2002, 06:37 PM
Originally posted by jemfinch


No, not at all. Emacs facilitates Python programming much better than Vim. That's a simple fact. If the style guide of Python talked about Vim, I'd be upset, because while Vim is a great editor, it simply doesn't provide all the tools and conveniences that Emacs offers for programming in Python. To promote Vim over Emacs would be to promote an inferior product, and that's just not the Pythonic Way.

Jeremy

Editing-wise, Emacs is vastly inferior to Vim. Vim may be less suited for programming (though I absolutely don't feel that way) than Emacs since the latter has all these programming modes, but you have to admit that the default Emacs key-binding is absolutely disgusting. It hurts the wrists pretty bad (ask RMS, who has carpal tunnel syndrome). If there was a mode for Emacs like vimpire or something that would have all Vim key bindings, I would probably use Emacs, but since right now it doesn't, I won't use it. And anyone who has used Vim as extensively as I have (Strike, Brad, kmj) knows that vi keybinding is not enough when you've used the true power of Vim (visual mode, folding, macros, some cool z and g commands, etc.), so don't bother telling me about viper-mode.

jemfinch
08-06-2002, 11:21 PM
Originally posted by GnuVince
but you have to admit that the default Emacs key-binding is absolutely disgusting.


Who said you had to use them?


If there was a mode for Emacs like vimpire or something that would have all Vim key bindings, I would probably use Emacs, but since right now it doesn't, I won't use it.


Such a module would be terribly inefficient. Once you start getting into the more complex stuff that Vim supports, you'll be chording at least as much as Emacs. You'd find that such a module held you back more than it helped you.


And anyone who has used Vim as extensively as I have (Strike, Brad, kmj) knows that vi keybinding is not enough when you've used the true power of Vim (visual mode, folding, macros, some cool z and g commands, etc.), so don't bother telling me about viper-mode.

And anyone who has used Emacs as extensively as I have (Guido van Rossum, Eric S. Raymond, Richard Stallman) knows that vim is not enough when you're used to the true power of Emacs (programming in Emacs' Lisp, integrating with debuggers/shells/interactive loops, class browsing, etc.), so don't bother telling me about vim :wave:

Jeremy

(You don't have to bother replying to this to try and convince me of Vim's merits, I fully recognize Vim's merits and make use of them when I have to (that is, when I don't have Emacs installed) but I still prefer Emacs.)

GnuVince
08-07-2002, 12:45 AM
Jeremy: I've long given up trying to convince hardcore Emacs users to use Vim. But I guess most Emacs people have also given up trying to convince the Vim people.

So, here's what I'm gonna tell you: switch to wheat bread!

jemfinch
08-07-2002, 01:50 PM
Originally posted by GnuVince
Jeremy: I've long given up trying to convince hardcore Emacs users to use Vim. But I guess most Emacs people have also given up trying to convince the Vim people.


As I have, really. I was just in defend-emacs-mode back there :) My advocate-emacs-mode doesn't work as much anymore.


So, here's what I'm gonna tell you: switch to wheat bread!

I already use wheat bread, of course. With little sunflower seeds in it and all.

Jeremy

darelf
08-08-2002, 10:01 AM
Umm... despite the un-intentional starting of a flame war which consumed half the west coast......

I've cleaned up the code a bit and reorganized the world code. *sigh* But, I suppose the world is now easier to deal with by names rather than numbers. Though, now, I have to worry about name collisions. But with a file scheme, that should work well.

I'm thinking about putting in a timer that saves the entire world as a binary pickle every so often, in case of a crash.
Then having separate room files that are loaded, and can be loaded from inside the mud....

and example would be:
(in the world code)

def loadRoom(self, r):
execfile(r)



This would mean a file that contains the python code to add a room to the world.
Also, loading a file that describes a room that already exists, would basically replace the current room with the new definition.

Problem one: Someone is in a room that is being replaced.... any suggestions?
Problem two: Someone puts a huge file that doesn't belong there in place of a room file causing it to try to read the whole thing in.
Problem three: Someone puts invalid commands in the room file...

Any suggestions? Hints? (I'm thinking a try around the execfile and catch the exception?)

kmj
08-08-2002, 10:47 AM
darelf: that wasn't a flamewar.

darelf
08-09-2002, 09:54 AM
Originally posted by kmj
darelf: that wasn't a flamewar.

As you may have noticed, I have a penchant for exaggeration...

EDIT: couldn't spell "noticed" the first time... sheesh

darelf
08-09-2002, 10:15 AM
BTW: The script is now being held at ftp://mythicrpg.com/mud.py

I'll probably update it there every once in a while....

jemfinch
08-11-2002, 05:02 AM
Originally posted by darelf

and example would be:
(in the world code)

def loadRoom(self, r):
execfile(r)



This would mean a file that contains the python code to add a room to the world.
Also, loading a file that describes a room that already exists, would basically replace the current room with the new definition.

Problem one: Someone is in a room that is being replaced.... any suggestions?
Problem two: Someone puts a huge file that doesn't belong there in place of a room file causing it to try to read the whole thing in.
Problem three: Someone puts invalid commands in the room file...


Problem one, Simple Answer: Don't allow populated rooms to be reloaded.

Problem one, Slightly-more-complex Answer: Have each room keep a list of "contained objects" and use this list in the new version of the room when updating a room.

Problem two: See Problem three.

Problem three: Do what I did with dynamically loadable plugins on my IRC bot. First, determine some specific "interface" a module should have to be a "dynamically loadable room module" -- e.g., in my IRC bot, a dynamically loadable module must have a "Class" attribute, which is then instantiated and stuck into the running Irc instance. When someone wants to load a room, dynamically (using either __import__ or the imp module) load the module they want to load (this does all the syntax-checking and whatnot) and then use that interface you designed to insert the new (or replace the old) room. It's important here that that the interface be entirely a set of operations and data that the module provides, and in no way involves the rest of the program at all. You don't want dynamically loadable modules munging up the rest of your program.

Jeremy

Any suggestions? Hints? (I'm thinking a try around the execfile and catch the exception?) [/QUOTE]