2 Replies - 2066 Views - Last Post: 22 March 2012 - 06:57 AM Rate Topic: -----

#1 SnoeMarx  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 4
  • Joined: 26-February 12

Understanding a Simple Asynchronous Telnet Server

Posted 26 February 2012 - 11:09 AM

Hi there everyone,
I've been messing around with programming with the miniboa module recently, and I'm confused as to how to wait for one particular user's input. I'm trying to create a MUD-like telnet server that pits a board game engine that I wrote against a client. The problem is most likely coming from a lack of understanding of how asynchronous networking works, but here are my goals with the program.

1) Allow clients to connect to my computer.
2) Allow clients to chat in the lobby
OR
3) Once a client enters a command, the server will play a game with the client.

The way that my board game engine works offline is relatively simple;

1) Prints the board to the user.
2) Waits on user input.
3) If the move is legal, it prints a new board with the user's move.
4) Prints a new board with the computer's move.
5) Repeat steps 2-5.

My confusion comes from a conflict between the way I want the engine to work online, and if it can be applied to an asynchronous server. Is this even reasonable? I tried to make a loop that waits until a valid move has been made by the client, but it just sticks the client and server in an infinite loop.

def interface(client):
    print("Game interface started for %s\n" % (client.addrport()))
    if client.active and client.cmd_ready:
        client.send("Hello, and welcome to my first ever igo engine!\n")
        client.send("I hope to make it around 20k one day, but right now\n")
        client.send("it plays on a 50k level. It's good for a time-killer,\n")
        client.send("and even better to take out your frustrations! Now\n")
        client.send("that the introductions are out of the way, let's\n")
        client.send("play!\n\n")
        client.send("What sized board do you want to play on?\n")
        
        valid_size = False
        while valid_size == False:
            try:
                size = int(client.get_command())
                if size <= 19 and size >= 2:
                    valid_size = True
                else:
                    client.send("Oh no! That's not a valid board size!")
            except ValueError:
                client.send("Oh no! That's not a valid board size!")
        self.single_player_match(client, size)


I've also tried to just explicitly call the client.get_command(), but it just returns the None type.

def test(client):
    client.send("This is some test text. Thanks!\n")
    cmd = client.get_command()
    print("You entered: %s\n" % cmd)


I probably have the completely wrong idea about my entire approach, so a push in the right direction would be appreciated.

Thanks,
Snoe

Is This A Good Question/Topic? 0
  • +

Replies To: Understanding a Simple Asynchronous Telnet Server

#2 SnoeMarx  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 4
  • Joined: 26-February 12

Re: Understanding a Simple Asynchronous Telnet Server

Posted 26 February 2012 - 01:48 PM

I've made some progress with my engine! I figured out that the way I called my data was entirely wrong. This is my new approach:

def menu(client):
    print("Game interface started for %s\n" % (client.addrport()))
    CLIENT_IN_MENU.append(client)
    client.send("Hello, and welcome to my first ever igo engine!\n")
    client.send("I hope to make it around 20k one day, but right now\n")
    client.send("it plays on a 50k level. It's good for a time-killer,\n")
    client.send("and even better to take out your frustrations! Now\n")
    client.send("that the introductions are out of the way, let's\n")
    client.send("play!\n\n")
    client.send("What sized board do you want to play on?\n")
    
def menu_choice(client):
    global SERVER_RUN
    size = client.get_command()
    try:
        size = int(size)
    except TypeError or ValueError:
        client.send("Oh no! That's not a valid board size!\n")
    if size <= 19 and size >= 2:
        CLIENT_IN_MENU.remove(client)
        debug_menu(client, size)
    else:
        client.send("Oh no! That's not a valid board size!\n")


To clarify, I'll try to make a flow chart of both processes' intents. The old method's plan was something like this:

1) Get input from clients in the main loop.
2) Check if the text entered was in the command list. If so, it would execute the command. If not, it would echo the chat to all other clients.
3) The command would ask the client for input through a loop that verified if the input was valid (this would work for synchronous networks, not asynchronous in theory. In my example it tried to pull non-existent data from itself, while avoiding collecting actual input from the main loop.)
4) The server would interpret the command and start a game. (I didn't get this far, but this is where I wanted to go.)

This is the new method:


1) Get input from clients in the main loop.
2) Check if the text entered was in the command list. If so, it would execute the command. If not, it would echo the chat to all other clients.
3) The command would turn on a switch for that specific client.
4) Ask for input, then put the user back in the main loop.
5) It would then make sure that all input would be directed toward the command.
6) If the input is valid, it would direct the variable that the input is stored in to the game.
7) The game itself would handle it's input in a similar manner, except it appends data to a list in the following format: [client, game_number].

I haven't gotten to the actual game engine bit yet, but I think now that I've identified this problem, it'll be smooth sailing here on out.
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: Understanding a Simple Asynchronous Telnet Server

Posted 22 March 2012 - 06:57 AM

if it helps, here is code for a MUD server that I am working on, that baavgai had helped me out with.

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



Was This Post Helpful? 0
  • +
  • -

Page 1 of 1