13 Replies - 5371 Views - Last Post: 13 July 2011 - 05:33 AM Rate Topic: -----

#1 hlx  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 31
  • View blog
  • Posts: 135
  • Joined: 13-November 10

Python MUD question

Posted 09 July 2011 - 01:04 PM

Hello there, I am looking for a little bit of theory to be answered, if possible.

I am currently working on a MUD made in python, using the miniboa telnet framework. I can communicate between my clients, and I am currently working on a finite state machine to define what actions the players can do, when.

My big question mark is: NPC's. How would I go about implementing them?

Since this server is not threaded, I will assume some sort of timer will be sweeping across all the rooms, updating npc's as they go.

However; what I do not understand is how the NPC's actually "enter" rooms in a MUD.

My room file looks like:

[ROOMID] = 1
[DESC] = "An example room."
[NPCS] = 1



my npc file:

[NPCID] = 1
[NAME] = "Cairn"
[DESC] = "test"
[STARTROOM] = "1"

and so on and so forth. So my room loading script will look for npc's in their room. But in pseudocode, how do I actually make them part of the game aside from notifying the player that they are there? In an asynchronous environment, how does one make an NPC respond to commands to one player only?

here is my current "statemachine" so far, as some code is required.

"""
Game States: (We cannot have loops, as this will tie up the server, so we use a Finite State Machine
held by: client.GAME_STATE = int

	0: Login State
	1: New State
	2: Non-New Authentication
	3: Gameplay, entry into chat server
	4: 
	5: 
	6:
	7:
	8:
	9: Dead
	10: Jailed, stuck in a lifeless room (perhaps with duties to do, or submit to a penalty that will subtract a few levels.)
"""
from fadedmud import *

def statemachine(client):
    
    msg = client.get_command()
    
    if client.GAME_STATE == 0:
        client.send("> ")
        if msg == "hello":
            client.GAME_STATE = 1
            client.send("YOU ARE IN STATE 1\n")
	
    if client.GAME_STATE == 1:
        client.send("100/100> ")




NOTE: I am not looking for someone to code my game for me, I just need some inspiration in the right direction. Thank you.

Is This A Good Question/Topic? 0
  • +

Replies To: Python MUD question

#2 Motoma  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 452
  • View blog
  • Posts: 796
  • Joined: 08-June 10

Re: Python MUD question

Posted 09 July 2011 - 01:27 PM

First, I would like to preface this post with the fact that I'm unfamiliar with miniboa.

In the most basic instance, each NPC would have a timer that keeps track of how many turns until it moves. The server would iterate over every NPC checking to see if that NPC's timer is 0, and when it is it would reset it and make the NPC do its task.

# In the game's main loop
for npc in allnpcs:
  if npc.counter < 0:
    npc.counter -= 1
  else:
    npc.counter = npc.turn_speed
...



In a more advanced scenario, you would could create a priority queue containing all of the NPCs with the priority set to the amount of time until the NPC's next move. As NPCs are added or removed from the queue, times are updated.
Was This Post Helpful? 0
  • +
  • -

#3 hlx  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 31
  • View blog
  • Posts: 135
  • Joined: 13-November 10

Re: Python MUD question

Posted 09 July 2011 - 01:40 PM

That's fair. I take it that would make each npc do whatever actions it was told to (if any) at that point. What I am having trouble wrapping my head around is making them semi-sentient, reacting to commands.
Was This Post Helpful? 0
  • +
  • -

#4 atraub  Icon User is offline

  • Pythoneer
  • member icon

Reputation: 759
  • View blog
  • Posts: 2,010
  • Joined: 23-December 08

Re: Python MUD question

Posted 11 July 2011 - 06:15 AM

Give us an example of desired NPC behavior :)
Was This Post Helpful? 0
  • +
  • -

#5 baavgai  Icon User is online

  • Dreaming Coder
  • member icon

Reputation: 5805
  • View blog
  • Posts: 12,644
  • Joined: 16-October 07

Re: Python MUD question

Posted 11 July 2011 - 08:28 AM

This looks like a lot of fun, so I had to give it a spin.

Here is a simple example of a room with an NPC in it. What's important is that the NPC and the client both contain state, so their interactions are consistent. Also, other clients share the state of the game, so if one makes the butler run from the room, the others see the action.

I tried to abstract the miniboa library a little to make it an easier framework. However, the client state lookup is a drag. It would probably be easier to just apply a wrapper to the client from the start, but I didn't want to add confusion.

For a true demo I'd want to flesh this out a little more, but this should give some idea.

#!/usr/bin/env python

from miniboa import TelnetServer

class BaseMudClass(object):
	def __init__(self, idle_timeout = 300):
		self.idle_timeout = idle_timeout
		self.clients = []
		self.running = False
		
		self.port = 7777
		self.address = ''
		self.timeout = 0.05

	def broadcast(self, msg):
		for client in self.clients:
			client.send(msg)

	def on_connect(self, client):
		print "++ Opened connection to %s" % client.addrport()
		self.clients.append(client)

	def on_disconnect(self, client):
		print "-- Lost connection to %s" % client.addrport()
		self.clients.remove(client)

	def kick_idle(self):
		for client in self.clients:
			if client.idle() > self.idle_timeout:
				print('-- Kicking idle client from %s' % client.addrport())
				client.active = False

	def process_clients(self):
		for client in self.clients:
			if client.active and client.cmd_ready:
				self.process_client(client)

	def process_client(self, client):
		pass


	def start(self):
		server = TelnetServer(
			port=self.port, 
			address=self.address,
			on_connect=self.on_connect,
			on_disconnect=self.on_disconnect,
			timeout=self.timeout
			)

		print(">> Listening for connections on port %d.  CTRL-C to break." % server.port)

		self.running = True
		while self.running:
			server.poll()
			self.kick_idle()
			self.process_clients()
		print(">> Server shutdown.")



class MyMud(BaseMudClass):
	ROOMS = [ 'NW','NE','SW','SE' ]
	FOOD_CHOICE = [ 'Blueberries', 'Strawberries', 'Mango' ]
	def __init__(self):
		BaseMudClass.__init__(self)
		# choice is to extend telnet client or simply track state on our own
		# we're going with the latter
		self.clientState = { }
		self.butler = { 'room' : 0 }


	def on_connect(self, client):
		BaseMudClass.on_connect(self, client)
		self.broadcast('%s checks in.\n' % client.addrport() )
		client.send("Welcome to the Hotel, %s.\n" % client.addrport() )
		self.clientState[client] = { 'room' : 0, 'lastChoice': -1, 'butlerOption' : False }

	def on_disconnect(self, client):
		BaseMudClass.on_disconnect(self, client)
		self.broadcast_room('%s poofs out of existence.\n' % client.addrport(), self.clientState[client]['room'])
		del self.clientState[client]

	def broadcast_room(self, msg, room):
		for client in self.clients:
			if self.clientState[client]['room']==room:
				client.send(msg)

	def sendHelpText(self, client):
		client.send('Options include: bye, look, talk butler\n')

	def process_client(self, client):
		cs = self.clientState[client]
		cmd = client.get_command().lower()
		
		if cmd=='bye':
			client.active = False
		elif cmd=='look':
			room = cs['room']
			msg = 'You are in the %s room.\n' % self.ROOMS[room]
			if self.butler['room']==room:
				msg += 'You see a butler.\n'
			client.send(msg)
		elif cmd=='talk butler':
			if not self.butler['room']==cs['room']:
				client.send('There is no butler here.\n')
			else:
				msg = 'Good evening.\n'
				if cs['lastChoice']==-1:
					msg += 'Might I interest you in one of the following:\n'
					msg += '\n'.join( '%d %s' % ((i+1),self.FOOD_CHOICE[i]) for i in range(len(self.FOOD_CHOICE)))
					msg += '\n'
					cs['butlerOption'] = True
				else:
					msg += 'Did you enjoy the %s?\n' % self.FOOD_CHOICE[cs['lastChoice']]
				client.send(msg)
		elif cs['butlerOption']:
			cs['butlerOption'] = False
			if cmd in ('1','2'):
				i = int(cmd)-1
				client.send('Very good sir, here are your %s\n' % self.FOOD_CHOICE[i])
				cs['lastChoice']=i
			elif cmd=='3':
				client.send('%s!  Oh, you frightful person!\n' % self.FOOD_CHOICE[2])
				cs['lastChoice']=2
				self.butler['room'] = 1
				self.broadcast_room('The butler runs from the room!\n', cs['room'])
				self.broadcast_room('A horrified butler enters the room\n', 1)
			else:
				self.sendHelpText(client)
		else:
			self.sendHelpText(client)


if __name__ == '__main__':
	mud = MyMud()
	mud.start()



Hope this helps.
Was This Post Helpful? 2
  • +
  • -

#6 hlx  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 31
  • View blog
  • Posts: 135
  • Joined: 13-November 10

Re: Python MUD question

Posted 11 July 2011 - 02:35 PM

That was some really good info, thank you. I was wondering if you guys could critique what I have set up so far (sans any npc scripting) , and let me know if it makes any sense, or is just a chaotic mess.

Fadedmud.py , the main script:

"""
MUD Server created by Justin Tokarchuk
Credits to Miniboa for telnet framework.
"""
from miniboa import TelnetServer
from util.statemachine import *
from util.console import *

IDLE_TIMEOUT = 5000
CLIENT_LIST = []
SERVER_RUN = True

#perhaps some createdb calling here?


MUD_NAME = "Faded Reality"


def on_connect(client):
    """
    Handles new clients that connect
    """
    print "++ Opened connection to %s" % client.addrport()
    
    CLIENT_LIST.append(client)
    client.send_cc("^WWelcome to ^R" + MUD_NAME + "^~" + "^W! This will be an ascii logo eventually\n\n^~(Press any key to continue.)\n\n")
 


def on_disconnect(client):
    """
    Sample on_disconnect function.
    Handles lost connections.
    """
    print "-- Lost connection to %s" % client.addrport()
    CLIENT_LIST.remove(client)
   


def kick_idle():
    """
    Looks for idle clients and disconnects them by setting active to False.
    """
    ## Who hasn't been typing?
    for client in CLIENT_LIST:
        if client.idle() > IDLE_TIMEOUT:
            print('-- Kicking idle lobby client from %s' % client.addrport())
            client.active = False


def process_clients():
    """
    Check each client, if client.cmd_ready == True then there is a line of
    input available via client.get_command().
    """
    
    for client in CLIENT_LIST:
        
        if client.active and client.cmd_ready:
            ## If the client sends input echo it to the chat room
            statemachine(client)

		
def broadcast(msg):
    """
    Send msg to every client.
    """
    for client in CLIENT_LIST:
        client.send(msg)


def chat(client): #unused at this point, will revamp
    """
    Echo whatever client types to everyone.
    """
    global SERVER_RUN
    msg = client.get_command()
    print '%s says, "%s"' % (client.addrport(), msg)

    for guest in CLIENT_LIST:
        if guest != client:
            guest.send('%s says, %s\n' % (client.addrport(), msg))
        else:
            guest.send('You say, %s\n' % msg)

    cmd = msg.lower()
    ## bye = disconnect
    if cmd == 'bye':
        client.active = False
    ## shutdown == stop the server
    elif cmd == 'shutdown':
        SERVER_RUN = False


#------------------------------------------------------------------------------
#       Main
#------------------------------------------------------------------------------

if __name__ == '__main__':

    ## Simple chat server to demonstrate connection handling via the
    ## async and telnet modules.

    ## Create a telnet server with a port, address,
    ## a function to call with new connections
    ## and one to call with lost connections.

    telnet_server = TelnetServer(
        port=7777,
        address='',
        on_connect=on_connect,
        on_disconnect=on_disconnect,
        timeout = .05
        )

    print("\n>> Listening for connections on port %d.  CTRL-C to break.\n"
        % telnet_server.port)


    ## Server Loop
    while SERVER_RUN:
        telnet_server.poll()        ## Send, Recv, and look for new connections
        kick_idle()                 ## Check for idle clients
        process_clients()           ## Check for client input

    print(">> Server shutdown.")



statemachine.py: (controls what users are where when they login/create accounts and finally

"""
Game States: (We cannot have loops, as this will tie up the server, so we use a Finite State Machine
held by: client.GAME_STATE = int

	0: Login State
	1: New State
	2: Non-New Authentication
	3: Gameplay, entry into chat server
	4: 
	5: 
	6:
	7:
	8:
	9: Dead
	10: Jailed, stuck in a lifeless room (perhaps with duties to do, or submit to a penalty that will subtract a few levels.)
"""

from util.fileoperations import *
from db.playerdb import *
from util.console import *

def statemachine(client):
    
    msg = client.get_command()
    msg = msg.lower()
	
    if client.GAME_STATE == 0: #Login or New Account Phase
        if msg != "new" and msg != "" and client.GAME_STATE == 0:
            # lookup user, see if they actually exist.
            if client.player_name == "null":
                client.player_name = filelookup('data\player', msg, 'plr')
                # If they do actually exist, attempt to load them here.
                if client.player_name != "null":
                    loadplr(client.player_name + ".plr")
                    client.GAME_STATE = 2
                      
        if msg == "new":
            client.GAME_STATE = 1
            msg = ""
                    
        if client.GAME_STATE == 0:
            client.send_cc('^WPlease enter your player name or type ^~"^Gnew^~" ^Wto create one:^~\n')
            client.send("> ")
        
 ######################################################################################################################           
    if client.GAME_STATE == 1: # Account creation Phase
        if client.GAME_STATE == 1 and msg == "" and client.player_name == "null" and client.player_creationstate == 0:
            client.send_cc("\n^WBy what name do you wish to be called?^G:^~ ")	
        if client.player_name == "null" and msg != "" and not os.path.exists("data/player/" + msg + ".plr"):
            client.player_name = msg
            # need a call in here to check a swearfilter.txt for bad names
            createplr(client.player_name)
            msg = ""
            client.player_creationstate = 1
        else: 
            if client.player_creationstate == 0 and os.path.exists("data/player/" + msg + ".plr"):
                msg = ""
                client.send_cc("\n^WERROR^W: ^RThat user already exists! Try again!^~\n")
                client.send_cc("\n^WBy what name do you wish to be called?^G:^~ ")
        #TODO: implement population of new player template, in playerDB likely.    
 #######################################################################################################################       
        if client.GAME_STATE == 1 and client.player_creationstate == 1:
            print "test"



PlayerDB.py (player lookup, save, load, etc (partial)


# Player Database Controller
import os
import shutil
from fadedmud import *
from miniboa import TelnetServer

def loadplr(filename):

    file = open("data\player\\" + filename, "r")
    print file
    for line in file:
        print line
        
        
def createplr(filename):
    shutil.copy2('data/player/template.txt','data/player/' + filename + '.plr')
    #file = open("data/player/" + filename + ".plr", "w")
    #file.close()
    print ">> User: " + filename + " was created on server."



Telnet.py - contains client-specific variables.

# -*- coding: utf-8 -*-
#------------------------------------------------------------------------------
#   miniboa/telnet.py
#   Copyright 2009 Jim Storch
#   Licensed under the Apache License, Version 2.0 (the "License"); you may
#   not use this file except in compliance with the License. You may obtain a
#   copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#   License for the specific language governing permissions and limitations
#   under the License.
#------------------------------------------------------------------------------

"""
Manage one Telnet client connected via a TCP/IP socket.
"""

import socket
import time

from miniboa.error import BogConnectionLost
from miniboa.xterm import colorize
from miniboa.xterm import word_wrap


#---[ Telnet Notes ]-----------------------------------------------------------
# (See RFC 854 for more information)
#
# Negotiating a Local Option
# --------------------------
#
# Side A begins with:
#
#    "IAC WILL/WONT XX"   Meaning "I would like to [use|not use] option XX."
#
# Side B replies with either:
#
#    "IAC DO XX"     Meaning "OK, you may use option XX."
#    "IAC DONT XX"   Meaning "No, you cannot use option XX."
#
#
# Negotiating a Remote Option
# ----------------------------
#
# Side A begins with:
#
#    "IAC DO/DONT XX"  Meaning "I would like YOU to [use|not use] option XX."
#
# Side B replies with either:
#
#    "IAC WILL XX"   Meaning "I will begin using option XX"
#    "IAC WONT XX"   Meaning "I will not begin using option XX"
#
#
# The syntax is designed so that if both parties receive simultaneous requests
# for the same option, each will see the other's request as a positive
# acknowledgement of it's own.
#
# If a party receives a request to enter a mode that it is already in, the
# request should not be acknowledged.

## Where you see DE in my comments I mean 'Distant End', e.g. the client.

UNKNOWN = -1

#--[ Telnet Commands ]---------------------------------------------------------

SE      = chr(240)      # End of subnegotiation parameters
NOP     = chr(241)      # No operation
DATMK   = chr(242)      # Data stream portion of a sync.
BREAK   = chr(243)      # NVT Character BRK
IP      = chr(244)      # Interrupt Process
AO      = chr(245)      # Abort Output
AYT     = chr(246)      # Are you there
EC      = chr(247)      # Erase Character
EL      = chr(248)      # Erase Line
GA      = chr(249)      # The Go Ahead Signal
SB      = chr(250)      # Sub-option to follow
WILL    = chr(251)      # Will; request or confirm option begin
WONT    = chr(252)      # Wont; deny option request
DO      = chr(253)      # Do = Request or confirm remote option
DONT    = chr(254)      # Don't = Demand or confirm option halt
IAC     = chr(255)      # Interpret as Command
SEND    = chr(001)      # Sub-process negotiation SEND command
IS      = chr(000)      # Sub-process negotiation IS command

#--[ Telnet Options ]----------------------------------------------------------

BINARY  = chr(  0)      # Transmit Binary
ECHO    = chr(  1)      # Echo characters back to sender
RECON   = chr(  2)      # Reconnection
SGA     = chr(  3)      # Suppress Go-Ahead
TTYPE   = chr( 24)      # Terminal Type
NAWS    = chr( 31)      # Negotiate About Window Size
LINEMO  = chr( 34)      # Line Mode


#-----------------------------------------------------------------Telnet Option

class TelnetOption(object):
    """
    Simple class used to track the status of an extended Telnet option.
    """
    def __init__(self):
        self.local_option = UNKNOWN     # Local state of an option
        self.remote_option = UNKNOWN    # Remote state of an option
        self.reply_pending = False      # Are we expecting a reply?


#------------------------------------------------------------------------Telnet

class TelnetClient(object):

    """
    Represents a client connection via Telnet.

    First argument is the socket discovered by the Telnet Server.
    Second argument is the tuple (ip address, port number).
    """

    def __init__(self, sock, addr_tup):
        self.protocol = 'telnet'
        self.active = True          # Turns False when the connection is lost
        self.sock = sock            # The connection's socket
        self.fileno = sock.fileno() # The socket's file descriptor
        self.address = addr_tup[0]  # The client's remote TCP/IP address
        self.port = addr_tup[1]     # The client's remote port
        self.terminal_type = 'unknown client' # set via request_terminal_type()
        self.use_ansi = True
        self.columns = 80
        self.rows = 24
        self.send_pending = False
        self.send_buffer = ''
        self.recv_buffer = ''
        self.bytes_sent = 0
        self.bytes_received = 0
        self.cmd_ready = False
        self.command_list = []
        self.connect_time = time.time()
        self.last_input_time = time.time()
        self.GAME_STATE = 0
        
        
        #Player Specific Variables
        self.player_name = "null"
        self.player_creationstate = 0
        self.player_hp = 0
        self.player_mp = 0
        self.player_exp = 0
        self.player_exptolevel = 0
        self.player_level = 0
        self.player_str = 0
        self.player_vit = 0
        self.player_dex = 0
        self.player_mys = 0
        self.player_inventory = []
        self.player_gold = 0
        self.player_armor = 0
        self.player_weapon = 0
        self.player_attack = 0
        self.player_defense = 0
        self.player_magicattack = 0
        self.player_race = 0
        self.player_class = 0
        self.player_skills = []
        self.player_region = 0
        self.player_jailed = 0
        self.player_banned = 0
        self.player_warninglvl = 0
        self.player_guild = 0
        
        
        
        ## State variables for interpreting incoming telnet commands
        self.telnet_got_iac = False # Are we inside an IAC sequence?
        self.telnet_got_cmd = None  # Did we get a telnet command?
        self.telnet_got_sb = False  # Are we inside a subnegotiation?
        self.telnet_opt_dict = {}   # Mapping for up to 256 TelnetOptions
        self.telnet_echo = False    # Echo input back to the client?
        self.telnet_echo_password = False  # Echo back '*' for passwords?
        self.telnet_sb_buffer = ''  # Buffer for sub-negotiations

#    def __del__(self):

#        print "Telnet destructor called"
#        pass

    def get_command(self):
        """
        Get a line of text that was received from the DE. The class's
        cmd_ready attribute will be true if lines are available.
        """
        cmd = None
        count = len(self.command_list)
        if count > 0:
            cmd = self.command_list.pop(0)
        ## If that was the last line, turn off lines_pending
        if count == 1:
            self.cmd_ready = False
        return cmd

    def send(self, text):
        """
        Send raw text to the distant end.
        """
        if text:
            self.send_buffer += text.replace('\n', '\r\n')
            self.send_pending = True

    def send_cc(self, text):
        """
        Send text with caret codes converted to ansi.
        """
        self.send(colorize(text, self.use_ansi))

    def send_wrapped(self, text):
        """
        Send text padded and wrapped to the user's screen width.
        """
        lines = word_wrap(text, self.columns)
        for line in lines:
            self.send_cc(line + '\n')

    def deactivate(self):
        """
        Set the client to disconnect on the next server poll.
        """
        self.active = False

    def addrport(self):
        """
        Return the DE's IP address and port number as a string.
        """
        return "%s:%s" % (self.address, self.port)

    def idle(self):
        """
        Returns the number of seconds that have elasped since the DE
        last sent us some input.
        """
        return time.time() - self.last_input_time

    def duration(self):
        """
        Returns the number of seconds the DE has been connected.
        """
        return time.time() - self.connect_time

    def request_do_sga(self):
        """
        Request DE to Suppress Go-Ahead.  See RFC 858.
        """
        self._iac_do(SGA)
        self._note_reply_pending(SGA, True)

    def request_will_echo(self):
        """
        Tell the DE that we would like to echo their text.  See RFC 857.
        """
        self._iac_will(ECHO)
        self._note_reply_pending(ECHO, True)
        self.telnet_echo = True

    def request_wont_echo(self):
        """
        Tell the DE that we would like to stop echoing their text.
        See RFC 857.
        """
        self._iac_wont(ECHO)
        self._note_reply_pending(ECHO, True)
        self.telnet_echo = False

    def password_mode_on(self):
        """
        Tell DE we will echo (but don't) so typed passwords don't show.
        """
        self._iac_will(ECHO)
        self._note_reply_pending(ECHO, True)

    def password_mode_off(self):
        """
        Tell DE we are done echoing (we lied) and show typing again.
        """
        self._iac_wont(ECHO)
        self._note_reply_pending(ECHO, True)

    def request_naws(self):
        """
        Request to Negotiate About Window Size.  See RFC 1073.
        """
        self._iac_do(NAWS)
        self._note_reply_pending(NAWS, True)

    def request_terminal_type(self):
        """
        Begins the Telnet negotiations to request the terminal type from
        the client.  See RFC 779.
        """
        self._iac_do(TTYPE)
        self._note_reply_pending(TTYPE, True)

    def socket_send(self):
        """
        Called by TelnetServer when send data is ready.
        """
        if len(self.send_buffer):
            try:
                sent = self.sock.send(self.send_buffer)
            except socket.error, err:
                print("!! SEND error '%d:%s' from %s" % (err[0], err[1],
                    self.addrport()))
                self.active = False
                return
            self.bytes_sent += sent
            self.send_buffer = self.send_buffer[sent:]
        else:
            self.send_pending = False

    def socket_recv(self):
        """
        Called by TelnetServer when recv data is ready.
        """
        try:
            data = self.sock.recv(2048)
        except socket.error, ex:
            print ("?? socket.recv() error '%d:%s' from %s" %
                (ex[0], ex[1], self.addrport()))
            raise BogConnectionLost()

        ## Did they close the connection?
        size = len(data)
        if size == 0:
            raise BogConnectionLost()

        ## Update some trackers
        self.last_input_time = time.time()
        self.bytes_received += size

        ## Test for telnet commands
        for byte in data:
            self._iac_sniffer(byte)

        ## Look for newline characters to get whole lines from the buffer
        while True:
            mark = self.recv_buffer.find('\n')
            if mark == -1:
                break
            cmd = self.recv_buffer[:mark].strip()
            self.command_list.append(cmd)
            self.cmd_ready = True
            self.recv_buffer = self.recv_buffer[mark+1:]

    def _recv_byte(self, byte):
        """
        Non-printable filtering currently disabled because it did not play
        well with extended character sets.
        """
        ## Filter out non-printing characters
        #if (byte >= ' ' and byte <= '~') or byte == '\n':
        if self.telnet_echo:
            self._echo_byte(byte)
        self.recv_buffer += byte

    def _echo_byte(self, byte):
        """
        Echo a character back to the client and convert LF into CR\LF.
        """
        if byte == '\n':
            self.send_buffer += '\r'
        if self.telnet_echo_password:
            self.send_buffer += '*'
        else:
            self.send_buffer += byte

    def _iac_sniffer(self, byte):
        """
        Watches incomming data for Telnet IAC sequences.
        Passes the data, if any, with the IAC commands stripped to
        _recv_byte().
        """
        ## Are we not currently in an IAC sequence coming from the DE?
        if self.telnet_got_iac is False:

            if byte == IAC:
                ## Well, we are now
                self.telnet_got_iac = True
                return

            ## Are we currenty in a sub-negotion?
            elif self.telnet_got_sb is True:
                ## Sanity check on length
                if len(self.telnet_sb_buffer) < 64:
                    self.telnet_sb_buffer += byte
                else:
                    self.telnet_got_sb = False
                    self.telnet_sb_buffer = ""
                return

            else:
                ## Just a normal NVT character
                self._recv_byte(byte)
                return

        ## Byte handling when already in an IAC sequence sent from the DE

        else:

            ## Did we get sent a second IAC?
            if byte == IAC and self.telnet_got_sb is True:
                ## Must be an escaped 255 (IAC + IAC)
                self.telnet_sb_buffer += byte
                self.telnet_got_iac = False
                return

            ## Do we already have an IAC + CMD?
            elif self.telnet_got_cmd:
                ## Yes, so handle the option
                self._three_byte_cmd(byte)
                return

            ## We have IAC but no CMD
            else:

                ## Is this the middle byte of a three-byte command?
                if byte == DO:
                    self.telnet_got_cmd = DO
                    return

                elif byte == DONT:
                    self.telnet_got_cmd = DONT
                    return

                elif byte == WILL:
                    self.telnet_got_cmd = WILL
                    return

                elif byte == WONT:
                    self.telnet_got_cmd = WONT
                    return

                else:
                    ## Nope, must be a two-byte command
                    self._two_byte_cmd(byte)


    def _two_byte_cmd(self, cmd):
        """
        Handle incoming Telnet commands that are two bytes long.
        """
        #print "got two byte cmd %d" % ord(cmd)

        if cmd == SB:
            ## Begin capturing a sub-negotiation string
            self.telnet_got_sb = True
            self.telnet_sb_buffer = ''

        elif cmd == SE:
            ## Stop capturing a sub-negotiation string
            self.telnet_got_sb = False
            self._sb_decoder()

        elif cmd == NOP:
            pass

        elif cmd == DATMK:
            pass

        elif cmd == IP:
            pass

        elif cmd == AO:
            pass

        elif cmd == AYT:
            pass

        elif cmd == EC:
            pass

        elif cmd == EL:
            pass

        elif cmd == GA:
            pass

        else:
            print "2BC: Should not be here."

        self.telnet_got_iac = False
        self.telnet_got_cmd = None

    def _three_byte_cmd(self, option):
        """
        Handle incoming Telnet commmands that are three bytes long.
        """
        cmd = self.telnet_got_cmd
        #print "got three byte cmd %d:%d" % (ord(cmd), ord(option))

        ## Incoming DO's and DONT's refer to the status of this end

        #---[ DO ]-------------------------------------------------------------

        if cmd == DO:

            if option == BINARY:

                if self._check_reply_pending(BINARY):
                    self._note_reply_pending(BINARY, False)
                    self._note_local_option(BINARY, True)

                elif (self._check_local_option(BINARY) is False or
                        self._check_local_option(BINARY) is UNKNOWN):
                    self._note_local_option(BINARY, True)
                    self._iac_will(BINARY)
                    ## Just nod

            elif option == ECHO:

                if self._check_reply_pending(ECHO):
                    self._note_reply_pending(ECHO, False)
                    self._note_local_option(ECHO, True)

                elif (self._check_local_option(ECHO) is False or
                        self._check_local_option(ECHO) is UNKNOWN):
                    self._note_local_option(ECHO, True)
                    self._iac_will(ECHO)
                    self.telnet_echo = True

            elif option == SGA:

                if self._check_reply_pending(SGA):
                    self._note_reply_pending(SGA, False)
                    self._note_local_option(SGA, True)

                elif (self._check_local_option(SGA) is False or
                        self._check_local_option(SGA) is UNKNOWN):
                    self._note_local_option(SGA, True)
                    self._iac_will(SGA)
                    ## Just nod

            else:

                ## ALL OTHER OTHERS = Default to refusing once
                if self._check_local_option(option) is UNKNOWN:
                    self._note_local_option(option, False)
                    self._iac_wont(option)


        #---[ DONT ]-----------------------------------------------------------

        elif cmd == DONT:

            if option == BINARY:

                if self._check_reply_pending(BINARY):
                    self._note_reply_pending(BINARY, False)
                    self._note_local_option(BINARY, False)

                elif (self._check_local_option(BINARY) is True or
                        self._check_local_option(BINARY) is UNKNOWN):
                    self._note_local_option(BINARY, False)
                    self._iac_wont(BINARY)
                    ## Just nod

            elif option == ECHO:

                if self._check_reply_pending(ECHO):
                    self._note_reply_pending(ECHO, False)
                    self._note_local_option(ECHO, True)
                    self.telnet_echo = False

                elif (self._check_local_option(BINARY) is True or
                        self._check_local_option(BINARY) is UNKNOWN):
                    self._note_local_option(ECHO, False)
                    self._iac_wont(ECHO)
                    self.telnet_echo = False

            elif option == SGA:

                if self._check_reply_pending(SGA):
                    self._note_reply_pending(SGA, False)
                    self._note_local_option(SGA, False)

                elif (self._check_remote_option(SGA) is True or
                        self._check_remote_option(SGA) is UNKNOWN):
                    self._note_local_option(SGA, False)
                    self._iac_will(SGA)
                    ## Just nod

            else:

                ## ALL OTHER OPTIONS = Default to ignoring
                pass


        ## Incoming WILL's and WONT's refer to the status of the DE

        #---[ WILL ]-----------------------------------------------------------

        elif cmd == WILL:

            if option == ECHO:

                ## Nutjob DE offering to echo the server...
                if self._check_remote_option(ECHO) is UNKNOWN:
                    self._note_remote_option(ECHO, False)
                    # No no, bad DE!
                    self._iac_dont(ECHO)

            elif option == NAWS:

                if self._check_reply_pending(NAWS):
                    self._note_reply_pending(NAWS, False)
                    self._note_remote_option(NAWS, True)
                    ## Nothing else to do, client follow with SB

                elif (self._check_remote_option(NAWS) is False or
                        self._check_remote_option(NAWS) is UNKNOWN):
                    self._note_remote_option(NAWS, True)
                    self._iac_do(NAWS)
                    ## Client should respond with SB

            elif option == SGA:

                if self._check_reply_pending(SGA):
                    self._note_reply_pending(SGA, False)
                    self._note_remote_option(SGA, True)

                elif (self._check_remote_option(SGA) is False or
                        self._check_remote_option(SGA) is UNKNOWN):
                    self._note_remote_option(SGA, True)
                    self._iac_do(SGA)
                    ## Just nod

            elif option == TTYPE:

                if self._check_reply_pending(TTYPE):
                    self._note_reply_pending(TTYPE, False)
                    self._note_remote_option(TTYPE, True)
                    ## Tell them to send their terminal type
                    self.send('%c%c%c%c%c%c' % (IAC, SB, TTYPE, SEND, IAC, SE))

                elif (self._check_remote_option(TTYPE) is False or
                        self._check_remote_option(TTYPE) is UNKNOWN):
                    self._note_remote_option(TTYPE, True)
                    self._iac_do(TTYPE)


        #---[ WONT ]-----------------------------------------------------------

        elif cmd == WONT:

            if option == ECHO:

                ## DE states it wont echo us -- good, they're not suppose to.
                if self._check_remote_option(ECHO) is UNKNOWN:
                    self._note_remote_option(ECHO, False)
                    self._iac_dont(ECHO)

            elif option == SGA:

                if self._check_reply_pending(SGA):
                    self._note_reply_pending(SGA, False)
                    self._note_remote_option(SGA, False)

                elif (self._check_remote_option(SGA) is True or
                        self._check_remote_option(SGA) is UNKNOWN):
                    self._note_remote_option(SGA, False)
                    self._iac_dont(SGA)

                if self._check_reply_pending(TTYPE):
                    self._note_reply_pending(TTYPE, False)
                    self._note_remote_option(TTYPE, False)

                elif (self._check_remote_option(TTYPE) is True or
                        self._check_remote_option(TTYPE) is UNKNOWN):
                    self._note_remote_option(TTYPE, False)
                    self._iac_dont(TTYPE)

        else:
            print "3BC: Should not be here."

        self.telnet_got_iac = False
        self.telnet_got_cmd = None

    def _sb_decoder(self):
        """
        Figures out what to do with a received sub-negotiation block.
        """
        #print "at decoder"
        bloc = self.telnet_sb_buffer
        if len(bloc) > 2:

            if bloc[0] == TTYPE and bloc[1] == IS:
                self.terminal_type = bloc[2:]
                #print "Terminal type = '%s'" % self.terminal_type

            if bloc[0] == NAWS:
                if len(bloc) != 5:
                    print "Bad length on NAWS SB:", len(bloc)
                else:
                    self.columns = (256 * ord(bloc[1])) + ord(bloc[2])
                    self.rows = (256 * ord(bloc[3])) + ord(bloc[4])

                #print "Screen is %d x %d" % (self.columns, self.rows)

        self.telnet_sb_buffer = ''


    #---[ State Juggling for Telnet Options ]----------------------------------

    ## Sometimes verbiage is tricky.  I use 'note' rather than 'set' here
    ## because (to me) set infers something happened.

    def _check_local_option(self, option):
        """Test the status of local negotiated Telnet options."""
        if not self.telnet_opt_dict.has_key(option):
            self.telnet_opt_dict[option] = TelnetOption()
        return self.telnet_opt_dict[option].local_option

    def _note_local_option(self, option, state):
        """Record the status of local negotiated Telnet options."""
        if not self.telnet_opt_dict.has_key(option):
            self.telnet_opt_dict[option] = TelnetOption()
        self.telnet_opt_dict[option].local_option = state

    def _check_remote_option(self, option):
        """Test the status of remote negotiated Telnet options."""
        if not self.telnet_opt_dict.has_key(option):
            self.telnet_opt_dict[option] = TelnetOption()
        return self.telnet_opt_dict[option].remote_option

    def _note_remote_option(self, option, state):
        """Record the status of local negotiated Telnet options."""
        if not self.telnet_opt_dict.has_key(option):
            self.telnet_opt_dict[option] = TelnetOption()
        self.telnet_opt_dict[option].remote_option = state

    def _check_reply_pending(self, option):
        """Test the status of requested Telnet options."""
        if not self.telnet_opt_dict.has_key(option):
            self.telnet_opt_dict[option] = TelnetOption()
        return self.telnet_opt_dict[option].reply_pending

    def _note_reply_pending(self, option, state):
        """Record the status of requested Telnet options."""
        if not self.telnet_opt_dict.has_key(option):
            self.telnet_opt_dict[option] = TelnetOption()
        self.telnet_opt_dict[option].reply_pending = state


    #---[ Telnet Command Shortcuts ]-------------------------------------------

    def _iac_do(self, option):
        """Send a Telnet IAC "DO" sequence."""
        self.send('%c%c%c' % (IAC, DO, option))

    def _iac_dont(self, option):
        """Send a Telnet IAC "DONT" sequence."""
        self.send('%c%c%c' % (IAC, DONT, option))

    def _iac_will(self, option):
        """Send a Telnet IAC "WILL" sequence."""
        self.send('%c%c%c' % (IAC, WILL, option))

    def _iac_wont(self, option):
        """Send a Telnet IAC "WONT" sequence."""
        self.send('%c%c%c' % (IAC, WONT, option))



phew -- long post. Again, not looking for a line-by-line, just want to know if it makes sense.

This post has been edited by hlx: 11 July 2011 - 03:30 PM

Was This Post Helpful? 0
  • +
  • -

#7 baavgai  Icon User is online

  • Dreaming Coder
  • member icon

Reputation: 5805
  • View blog
  • Posts: 12,644
  • Joined: 16-October 07

Re: Python MUD question

Posted 11 July 2011 - 05:03 PM

If you're going to add dynamic attributes to the client object, you'll want to initialize them on the connection. You are allowed to say client.GAME_STATE = 1. However, if you say if client.GAME_STATE == 0 and that attribute never exists, you'll get an attribute error.

In your on_connect, assign the custom attributes:
def on_connect(client):
	print "++ Opened connection to %s" % client.addrport()

	client.GAME_STATE = 0
	client.player_name = None
	# any other stuff you're going to try to reference before you initalize it
	# ...
	CLIENT_LIST.append(client)


Was This Post Helpful? 0
  • +
  • -

#8 hlx  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 31
  • View blog
  • Posts: 135
  • Joined: 13-November 10

Re: Python MUD question

Posted 11 July 2011 - 05:14 PM

View Postbaavgai, on 11 July 2011 - 06:03 PM, said:

If you're going to add dynamic attributes to the client object, you'll want to initialize them on the connection. You are allowed to say client.GAME_STATE = 1. However, if you say if client.GAME_STATE == 0 and that attribute never exists, you'll get an attribute error.

In your on_connect, assign the custom attributes:
def on_connect(client):
	print "++ Opened connection to %s" % client.addrport()

	client.GAME_STATE = 0
	client.player_name = None
	# any other stuff you're going to try to reference before you initalize it
	# ...
	CLIENT_LIST.append(client)



Thank you very much. Does it look like an unholy mess or pretty much make sense?
Was This Post Helpful? 0
  • +
  • -

#9 baavgai  Icon User is online

  • Dreaming Coder
  • member icon

Reputation: 5805
  • View blog
  • Posts: 12,644
  • Joined: 16-October 07

Re: Python MUD question

Posted 12 July 2011 - 07:19 AM

Doesn't look bad, but there are a number of things that could be better...

My last comments were incorrect, I hadn't realized you directly changed the miniboa.telnet.TelnetClient class. So, first thing; don't do that! Consider classes other people create as unchangeable and go from there. There are a number of reasons for this, not the least of which is being able to use code updates from the developer of the class. Rather, write another class that tracks what you need and takes advantage of the existing class.

I showed one way to do this already; the least invasive way. In my example I simply made a dictionary to associate one object with my custom data. I actually didn't care for the approach, but it offered the advantage of not touching anything. If we're willing to mess with the object we get via on_connect, things can get a lot cleaner.

I went from the same example you did; the chat example. It's actually not the greatest example code; it's point is to be simple. We can do better.

Don't get hung up on the idea of a "state machine." Realistically, this is no different from how a web site processes data. Your client makes a request, you process the request and return output. Here you have a built in session by nature of their connection. You still need to authenticate them. You also need to save session info that needs to be saved.

It's complex and using tools like classes can help.

I'm afraid I went a little overboard with this... Here's code that will do the job.
from miniboa import TelnetServer

import os, os.path

# because globals suck
# classes to come

# IDLE_TIMEOUT = 5000
# CLIENT_LIST = []
# SERVER_RUN = True

# this is our client base
# we'll hold the telent client and do all the work here
class MudClientBase(object):
	def __init__(self, telnetClient, idle_timeout):
		self.telnetClient = telnetClient
		self.idle_timeout = idle_timeout

	def logout(self):
		# self.telnetClient.active = False
		self.telnetClient.deactivate()
		self.telnetClient = None

	# this is neat, ever time we check for active
	# we have a chance to invalidate them if they're not
	def isActive(self):
		if self.telnetClient:
			if self.telnetClient.active:
				if self.telnetClient.idle() > self.idle_timeout:
					print("-- Kicking idle client from {}".format(self.getName()))
					self.logout()
				else:
					return True
		return False

	# name is more useful
	# once they're logged in, we can give them a real name
	def getName(self):
		if self.isActive():
			return self.telnetClient.addrport()
		return None

	# wrapping telnetClient commands gives us complete control
	def hasCommand(self):
		return self.isActive() and self.telnetClient.cmd_ready

	def getCommand(self):
		if self.isActive():
			return self.telnetClient.get_command().lower().strip()
		return None

	def send(self, msg):
		if self.isActive():
			self.telnetClient.send(msg + '\n')

	def send_cc(self, text):
		if self.isActive():
			self.telnetClient.send_cc(text)



# this is a basic wrapper for a server
class MudServerBase(object):
	def __init__(self, idle_timeout):
		self.idle_timeout = idle_timeout
		self.clients = []
		self.running = False
	
		self.port = 7777
		self.address = ''
		self.timeout = 0.05
	
	
	# wrap the raw TelnetClient
	def addClient(self, client):
		client = MudClientBase(client, self.idle_timeout)
		self.clients.append(client)
		return client
		
	def showWelcome(self, client):
		client.send('Howdy stranger!')

	def onConnect(self, client):
		client = self.addClient(client)
		print("++ Opened connection to {}".format(client.getName()))
		self.showWelcome(client)

	def removeClient(self, client):
		if client in self.clients:
			if client.isActive():
				client.logout()
			print("-- Removed Client {}".format(client.getName()))
			self.clients.remove(client)

	def onDisconnect(self, client):
		print("-- Lost connection to {}".format(client.addrport()))
		found = [ c for c in self.clients if c.telnetClient==client ]
		if not found==[]:
			self.removeClient(found[0])

	def broadcast(self, msg):
		for client in self.clients:
			client.send(msg)

	def getTelnetServer(self):
		return TelnetServer(
			port=self.port, 
			address=self.address,
			on_connect=self.onConnect,
			on_disconnect=self.onDisconnect,
			timeout=self.timeout
			)

	def processClient(self, client):
		print("{} entered {}".format(client.getName(), client.getCommand()))

	def start(self):
		telnet_server = self.getTelnetServer()
		print("\n>> Listening for connections on port {}.  CTRL-C to break.\n".format(telnet_server.port))
		self.running = True
		while self.running:
			telnet_server.poll()
			# print telnet_server.clients
			for client in self.clients:
				if client.hasCommand():
					self.processClient(client)
		print(">> Server shutdown.")


# now the magic happens
# time for code specfic to our game

# this is our client
# with extra info goodness
class MudClient(MudClientBase):
	def __init__(self, telnetClient, idle_timeout):
		MudClientBase.__init__(self, telnetClient, idle_timeout)
		self.resetState()
		
	def resetState(self):
		# I'm not sure about the numeric state
		# however, for login it makes sense
		self.GAME_STATE = 0 


		# your OMG huge list of variables
		# note, player_* is kind of redundant
		# I don't have the heart to change it at the moment
		self.player_name = None # this is a Python null, very handy
		self.player_creationstate = 0
		self.player_hp = 0
		self.player_mp = 0
		self.player_exp = 0
		self.player_exptolevel = 0
		self.player_level = 0
		self.player_str = 0
		self.player_vit = 0
		self.player_dex = 0
		self.player_mys = 0
		self.player_inventory = []
		self.player_gold = 0
		self.player_armor = 0
		self.player_weapon = 0
		self.player_attack = 0
		self.player_defense = 0
		self.player_magicattack = 0
		self.player_race = 0
		self.player_class = 0
		self.player_skills = []
		self.player_region = 0
		self.player_jailed = 0
		self.player_banned = 0
		self.player_warninglvl = 0
		self.player_guild = 0

	def getName(self):
		if self.player_name:
			return self.player_name
		else:
			return MudClientBase.getName(self)

	def setName(self, name):
		self.player_name = name


# this our server
class MudServer(MudServerBase):
	# States: Login, New, NonNew, Play, Dead, Jailed
	(STATE_LOGIN, STATE_NEW, STATE_WAITING_NEW, STATE_LOGIN_DONE) = range(4)
	def __init__(self, idle_timeout):
		MudServerBase.__init__(self, idle_timeout)
		self.gameName = "Faded Reality"
	
	# time for our custome client
	def addClient(self, client):
		client = MudClient(client, self.idle_timeout)
		self.clients.append(client)
		return client
		
	def showWelcome(self, client):
		msg = "^WWelcome to ^R" + self.gameName + "^~" + "^W! This will be an ascii logo eventually\n\n"
		msg += "^~(Press any key to continue.)\n\n"
		client.send_cc(msg)


	def getFilenameForPlayer(self, name):
		return "data/player_{}.plr".format(name)
		
	def playerExists(self, name):
		return os.path.exists(self.getFilenameForPlayer(name))

	def loadPlayer(self, client):
		pass

	def savePlayer(self, client):
		pass


	def processLogin(self, client, cmd):
		def showNewMessage():
			client.send_cc('^WPlease enter your player name or type ^~"^Gnew^~" ^Wto create one:^~\n')
			client.send("> ")

		def showNamePrompt():
			client.send_cc("\n^WBy what name do you wish to be called?^G:^~ ")

		def welcomePlayer():
			client.GAME_STATE = MudServer.STATE_LOGIN_DONE
			client.send("Welcome " + client.getName() + ".  What do you want to do?")

		if client.GAME_STATE==MudServer.STATE_LOGIN:
			showNewMessage()
			client.GAME_STATE = MudServer.STATE_NEW
			
		elif client.GAME_STATE==MudServer.STATE_NEW:
			if cmd=='':
				showNewMessage()
			elif cmd=='new':
				showNamePrompt()
				client.GAME_STATE = MudServer.STATE_WAITING_NEW
			elif self.playerExists(cmd):
				client.setName(cmd)
				self.loadPlayer(client)
				welcomePlayer()
			else:
				client.send("There is no player by that name.")
				
		elif client.GAME_STATE==MudServer.STATE_WAITING_NEW:
			if cmd=='':
				showNamePrompt()
			elif self.playerExists(cmd):
				client.send_cc("\n^WERROR^W: ^RThat user already exists! Try again!^~\n")
				showNamePrompt()
			else:
				client.setName(cmd)
				self.savePlayer(client)
				welcomePlayer()
				
		else:
			return False
		return True

	# this is the "state" code
	def processClient(self, client):
		cmd = client.getCommand()
		if self.processLogin(client, cmd):
			if client.GAME_STATE == MudServer.STATE_LOGIN_DONE:
				print("Login {}".format(client.getName()))
		elif cmd=='bye':
			self.removeClient(client)
		else:
			client.send("Sorry {}, I don't know how to '{}'.".format(client.getName(), cmd))


if __name__ == '__main__':
	server = MudServer(5000)
	server.start()



Note, classes are you friends. The only real function you need worry about is processClient. You'll also want to implement loadPlayer and savePlayer. I'd put in a password for the user, too.

That's pretty much it for the nit picky details, after which you can start working on the fun bits.

Hope this helps. And that I didn't throw too much at you. I had fun.
Was This Post Helpful? 1
  • +
  • -

#10 hlx  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 31
  • View blog
  • Posts: 135
  • Joined: 13-November 10

Re: Python MUD question

Posted 12 July 2011 - 08:12 AM

I cannot thank you enough. This has been incredibly helpful, and I would +500 you if I could. This is the whole purpose of me trying to learn this language, was to do it via this game. I am happy you corrected me. Many thanks.
Was This Post Helpful? 0
  • +
  • -

#11 baavgai  Icon User is online

  • Dreaming Coder
  • member icon

Reputation: 5805
  • View blog
  • Posts: 12,644
  • Joined: 16-October 07

Re: Python MUD question

Posted 12 July 2011 - 08:59 AM

Glad I could help. I don't want to say a "corrected" you, though.

There is no single solution for any computer problem, even most trivial. One measure of success is if the thing runs and by just that measure a program can still be truly horrific mess.

Rather, there are approaches you can use that will cause you less pain in the long run, though they might be more work up front. Even the professionals disagree on which is better for what; there is no real best, only what works best for you.

The code I posted is how I'd do it today. As I worked through the project, I could change some sections entirely. Programs evolve. Don't worry about that; embrace it.

Have fun.
Was This Post Helpful? 1
  • +
  • -

#12 hlx  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 31
  • View blog
  • Posts: 135
  • Joined: 13-November 10

Re: Python MUD question

Posted 12 July 2011 - 06:42 PM

All works well, except that when I CLOSE the telnet window, my server now crashes. I have looked it over and cannot come up with the reason.my best guess is in the processclients section. Any ideas?

Edit: Error

C:\Users\Justin\Dropbox\fadedmud>python bin/fadedmud.py

>> Listening for connections on port 7777.  CTRL-C to break.

++ Opened connection to 127.0.0.1:23619
++ Login Accepted from: justin @ 127.0.0.1:23619
Traceback (most recent call last):
  File "bin/fadedmud.py", line 278, in <module>
    server.start()
  File "bin/fadedmud.py", line 122, in start
    telnet_server.poll()
  File "C:\Users\Justin\Dropbox\fadedmud\bin\miniboa\miniboa\async.py", line 184
, in poll
    self.clients[sock_fileno].socket_recv()
  File "C:\Users\Justin\Dropbox\fadedmud\bin\miniboa\telnet.py", line 334, in so
cket_recv
    raise BogConnectionLost()
miniboa.miniboa.error.BogConnectionLost

C:\Users\Justin\Dropbox\fadedmud>PAUSE
Press any key to continue . . .

This post has been edited by hlx: 12 July 2011 - 08:30 PM

Was This Post Helpful? 0
  • +
  • -

#13 baavgai  Icon User is online

  • Dreaming Coder
  • member icon

Reputation: 5805
  • View blog
  • Posts: 12,644
  • Joined: 16-October 07

Re: Python MUD question

Posted 13 July 2011 - 05:26 AM

Well, the code I offered doesn't really have a process clients section; it's just part of the main loop:

while self.running:
	telnet_server.poll()
	for client in self.clients:
		if client.hasCommand():
			self.processClient(client)



The code is moderately sneaky; the hasCommand checks for isActive and isActive will do an idle logout and cleanup.

I ran into kind of the opposite problem before:
def logout(self):
	self.telnetClient.deactivate()
	self.telnetClient = None



Note how I set the telnetClient to none? Well, without that, the telnet session never closed. The reason is that the telnetClient object still had a handle and was never destroyed. Garbage collection in Python, and most gc languages, can be quirky. You may not think you have a handle on an object, but you might.

I do not get your error.

Your error looks like the client connection is being talked to and is missing ( self.clients[sock_fileno].socket_recv() ). Somehow, you have a client object that isn't still live in the list. The async.py code should trap this. Indeed, in the version I have it does:

 try:
	  self.clients[sock_fileno].socket_recv()
 except BogConnectionLost:
	  self.clients[sock_fileno].deactivate()



I'd suggest you trap the error in your loop, if your version isn't. Or, check to see you have the newest version.
Was This Post Helpful? 0
  • +
  • -

#14 hlx  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 31
  • View blog
  • Posts: 135
  • Joined: 13-November 10

Re: Python MUD question

Posted 13 July 2011 - 05:33 AM

EDIT: Redacted, I had broken something in my willy nilly editting of the miniboa classes. My fault. Re downloaded miniboa and all is well. Thanks again.

This post has been edited by hlx: 13 July 2011 - 06:20 AM

Was This Post Helpful? 0
  • +
  • -

Page 1 of 1