home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / gnome-games-data / glchess / chess / fics / server.py < prev    next >
Encoding:
Python Source  |  2009-04-14  |  17.0 KB  |  562 lines

  1. # -*- coding: utf-8 -*-
  2. import socket
  3. import select
  4. import random
  5.  
  6. import style12
  7. import telnet
  8.  
  9. class TelnetDecoder(telnet.Decoder):
  10.     def __init__(self, decoder):
  11.         self.decoder = decoder
  12.         telnet.Decoder.__init__(self)
  13.  
  14.     def onData(self, data):
  15.         self.decoder._processData(data)
  16.  
  17.     def onInterruptProcess(self):
  18.         self.decoder.connection.abort()
  19.         
  20. class Server:
  21.     
  22.     def __init__(self):
  23.         """
  24.         """
  25.         self.clients = {}
  26.         self.clientsByUserName = {}
  27.  
  28.         self.adverts = {}
  29.         self.advertNumber = 1
  30.         self.games = {}
  31.         self.gameNumber = 1
  32.  
  33.     def addAdvert(self, client):
  34.         """
  35.         """
  36.         advert = Advert(client)
  37.         while True:
  38.             number = self.advertNumber
  39.             self.advertNumber += 1
  40.             if self.advertNumber > 999:
  41.                 self.advertNumber = 1
  42.             if not self.adverts.has_key(number):
  43.                 break;
  44.         advert.number = number
  45.         self.adverts[number] = advert
  46.         client.adverts[number] = advert
  47.         return advert
  48.  
  49.     def removeAdverts(self, adverts):
  50.         """
  51.         """
  52.         for advert in adverts:
  53.             advert.client.adverts.pop(advert.number)
  54.             self.adverts.pop(advert.number)
  55.         
  56.         notifyLine = '\n<sr>'
  57.         for advert in adverts:
  58.             notifyLine += ' %i' % advert.number
  59.         notifyLine += '\n'
  60.         for client in self.clients.itervalues():
  61.             if client.name is None:
  62.                 continue
  63.             client.send(notifyLine)
  64.             client.sendPrompt()
  65.  
  66.     def addGame(self, whiteClient, blackClient):
  67.         """
  68.         """
  69.         game = Game(whiteClient, blackClient)
  70.         while True:
  71.             number = self.gameNumber
  72.             self.gameNumber += 1
  73.             if self.gameNumber > 999:
  74.                 self.gameNumber = 1
  75.             if not self.games.has_key(number):
  76.                 break;
  77.         game.number = number
  78.         self.games[number] = game
  79.         return game       
  80.  
  81.     def reportMove(self, game, move):
  82.         """Report a move in a game.
  83.         
  84.         'game' is the game that the move is in (?)
  85.         'move' is the move that has occured (style12.Move)
  86.         """
  87.         pass
  88.     
  89.     def addConnection(self, connection):
  90.         """
  91.         """
  92.         client = Client(self, connection)
  93.         self.clients[connection] = client
  94.         client.start()
  95.  
  96.     def registerIncomingData(self, connection, data):
  97.         """
  98.         """
  99.         client = self.clients[connection]
  100.         client.registerIncomingData(data)
  101.     
  102.     def connectionClosed(self, connection, reason):
  103.         """
  104.         """
  105.         client = self.clients[connection]
  106.         if client.name is not None:
  107.             self.clientsByUserName.pop(client.name)
  108.         self.clients.pop(connection)
  109.     
  110.     def onOutgoingData(self, connection, data):
  111.         """
  112.         """
  113.         pass
  114.     
  115. class Challenge:
  116.     """
  117.     """
  118.     
  119.     def __init__(self, client):
  120.         self.client = client
  121.         
  122. class Game:
  123.     """
  124.     """
  125.     # Properties
  126.     number = -1
  127.     white  = None
  128.     black  = None
  129.     time   = 2
  130.     inc    = 12
  131.     matchType = 'pbu'
  132.     
  133.     # State
  134.     toMove = None
  135.     move   = 1
  136.     
  137.     def __init__(self, whiteClient, blackClient):
  138.         """
  139.         """
  140.         self.white = whiteClient
  141.         self.black = blackClient
  142.         self.toMove = self.white
  143.         
  144.     def genGameLine(self):
  145.         if self.toMove is self.white:
  146.             colour = 'W'
  147.         else:
  148.             colour = 'B'
  149.         if self.white.rating == 0:
  150.             whiteRating = '++++'
  151.         else:
  152.             whiteRating = '%04i' % self.white.rating
  153.         if self.black.rating == 0:
  154.             blackRating = '++++'
  155.         else:
  156.             blackRating = '%04i' % self.black.rating
  157.         return '%3i %s %s %s %s [%s %2i %3i] %s -%s (%2i-%2i) %s: %2i' % \
  158.                (self.number,
  159.                 whiteRating, self.white.name.rjust(10),
  160.                 blackRating, self.black.name.rjust(10),
  161.                 self.matchType.rjust(3), self.time, self.inc,
  162.                 '1:36'.rjust(6), '2:33'.rjust(6),
  163.                 35, 35,
  164.                 colour, self.move)
  165.  
  166. class Advert:
  167.     """
  168.     """
  169.     number = -1
  170.     client = None
  171.     time = 2
  172.     inc = 12
  173.     rated = False
  174.     colour = None
  175.     variant = 'standard'
  176.     autoStart = True
  177.     checkFormula = False
  178.     minRating = 0
  179.     maxRating = 9999
  180.     
  181.     def __init__(self, client):
  182.         self.client = client
  183.     
  184.     def getVariant(self):
  185.         # Can only modify standard games
  186.         if self.variant != 'standard':
  187.             return self.variant
  188.         
  189.         # Get the name of the game as specified by FICS
  190.         expectedDuration = self.time + (2 * self.inc / 3)
  191.         if expectedDuration < 3:
  192.             return 'lightning'
  193.         elif expectedDuration < 15:
  194.             return 'blitz'
  195.         else:
  196.             return 'standard'
  197.         
  198.     def genSoughtLine(self):
  199.         # 76 ++++ murcon             10   0 unrated blitz      [black]     0-1100 f
  200.         #--- |||| ----------------- ||| ||| ------------------ -------- |||| ||||
  201.         gameProps = '%3i %3i' % (self.time, self.inc)
  202.         if self.rated:
  203.             rating = 'rated'
  204.         else:
  205.             rating = 'unrated'
  206.         gameProps += ' ' + (rating + ' ' + self.getVariant()).ljust(18)
  207.         if self.colour is None:
  208.             gameProps += ' ' * 9
  209.         else:
  210.             gameProps += (' [' + self.colour + ']').ljust(9)
  211.         if self.client.rating == 0:
  212.             rating = '++++'
  213.         else:
  214.             rating = '%04i' % self.client.rating
  215.         line = '%3i %s %s %s %4i-%4i' % (self.number, rating, self.client.name.ljust(17), gameProps, self.minRating, self.maxRating)
  216.         if self.checkFormula:
  217.             line += ' f'
  218.         return line
  219.         
  220.     def genSeekLine(self):
  221.         #murcon (++++) seeking 10 0 unrated blitz [black] f ("play 76" to respond)
  222.         gameProps = '%i %i' % (self.time, self.inc)
  223.         if self.rated:
  224.             gameProps += ' rated'
  225.         else:
  226.             gameProps += ' unrated'
  227.         gameProps += ' ' + self.getVariant()
  228.         if self.colour is not None:
  229.             gameProps += ' [' + self.colour + ']'
  230.         if self.checkFormula:
  231.             self.variant += ' f'
  232.         if self.client.rating == 0:
  233.             rating = '++++'
  234.         else:
  235.             rating = '%04i' % self.client.rating
  236.         return '%s (%s) seeking %s ("play %i" to respond)' % (self.client.name, rating, gameProps, self.number)
  237.     
  238.     def genSeekMessage(self):
  239.         if self.rated:
  240.             rated = 'r'
  241.         else:
  242.             rated = 'u'
  243.         if self.colour is None:
  244.             colour = '?'
  245.         else:
  246.             colour = 'w' # FIXME: B
  247.         if self.autoStart:
  248.             automatic = 't'
  249.         else:
  250.             automatic = 'f'
  251.         if self.checkFormula:
  252.             formulaChecked = 't'
  253.         else:
  254.             formulaChecked = 'f'
  255.         # FIXME: rating flags
  256.         return '<s> %i w=%s ti=%02X rt=%i t=%i i=%i r=%s tp=%s c=%s rr=%i-%i a=%s f=%s' % \
  257.                (self.number, self.client.name, self.client.titles, self.client.rating,
  258.                 self.time, self.inc, rated, self.getVariant(),
  259.                 colour, self.minRating, self.maxRating, automatic, formulaChecked)
  260.  
  261. class Client:
  262.     """
  263.     """
  264.     buffer = ''
  265.  
  266.     name = None
  267.     rating = 0
  268.     
  269.     titles = 0x00
  270.     TITLE_UNREGISTERED               = 0x01
  271.     TITLE_COMPUTER                   = 0x02
  272.     TITLE_GRAND_MASTER               = 0x04
  273.     TITLE_INTERNATIONAL_MASTER       = 0x08
  274.     TITLE_FIDE_MASTER                = 0x10
  275.     TITLE_WOMAN_GRAND_MASTER         = 0x20
  276.     TITLE_WOMAN_INTERNATIONAL_MASTER = 0x40
  277.     TITLE_WOMAN_FIDE_MASTER          = 0x80
  278.  
  279.     LOGIN_SCREEN = 'MINI-FICS SERVER\n'
  280.     
  281.     STATE_LOGIN         = 'LOGIN'
  282.     STATE_PASSWORD      = 'PASSWORD'
  283.     STATE_NULL_PASSWORD = 'NULL_PASSWORD'
  284.     STATE_POST_LOGIN    = 'POST_LOGIN'
  285.     STATE_MAIN          = 'MAIN'
  286.     STATE_GAME          = 'GAME'
  287.     state = STATE_LOGIN
  288.     
  289.     def __init__(self, server, connection):
  290.         """
  291.         """
  292.         self.server = server
  293.         self.connection = connection
  294.         self.adverts = {}
  295.         self.telnetDecoder = TelnetDecoder(self)
  296.     
  297.     def checkPassword(self, user, password):
  298.         """
  299.         """
  300.         return True
  301.     
  302.     def registerIncomingData(self, data):
  303.         """
  304.         """
  305.         self.telnetDecoder.registerIncomingData(data)
  306.  
  307.     def _processData(self, data):
  308.         """
  309.         """
  310.         self.buffer += data
  311.         
  312.         while True:
  313.             index = self.buffer.find('\n')
  314.             if index < 0:
  315.                 return
  316.             line = self.buffer[:index]
  317.             self.buffer = self.buffer[index + 1:]
  318.             if line[-1] == '\r':
  319.                 line = line[:-1]
  320.             self.processLine(line)
  321.  
  322.     def send(self, data):
  323.         self.server.onOutgoingData(self.connection, data)
  324.  
  325.     def start(self):
  326.         """
  327.         """
  328.         self.send(self.LOGIN_SCREEN)
  329.         self.setState(self.STATE_LOGIN)
  330.         
  331.     def setState(self, state):
  332.         if state is self.STATE_LOGIN:
  333.             self.send('login: ')
  334.         elif state is self.STATE_PASSWORD:
  335.             self.send('password: ')
  336.         elif state is self.STATE_POST_LOGIN:
  337.             self.send('**** Starting FICS session as %s ****\n' % self.name)
  338.             self.send('<sc>\n')
  339.             for ad in self.server.adverts.itervalues():
  340.                 self.send(ad.genSeekMessage() + '\n')
  341.         elif state is self.STATE_MAIN:
  342.             self.sendPrompt()
  343.         self.state = state
  344.         
  345.     def sendPrompt(self):
  346.         self.send('fics% ')
  347.  
  348.     def generateGuestLogin(self):
  349.         # Get a unique name
  350.         while True:
  351.             name = 'Guest'
  352.             for i in xrange(4):
  353.                 name += random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
  354.             if not self.server.clientsByUserName.has_key(name):
  355.                 break
  356.  
  357.         return name
  358.  
  359.     def processLine(self, line):
  360.         state = self.state
  361.         if state is self.STATE_LOGIN:
  362.             if line == '':
  363.                 self.setState(self.STATE_LOGIN)
  364.             elif line == 'guest':
  365.                 self.name = self.generateGuestLogin()
  366.                 self.send('Press return to enter the server as "%s":\n' % self.name)
  367.                 self.setState(self.STATE_NULL_PASSWORD)
  368.             else:
  369.                 if line.isalpha():
  370.                     self.name = line
  371.                     self.setState(self.STATE_PASSWORD)
  372.                 else:
  373.                     self.send('Sorry, names can only consist of lower and upper case letters.  Try again.\n')
  374.                     self.setState(self.STATE_LOGIN)
  375.             
  376.         elif state is self.STATE_NULL_PASSWORD:
  377.             self.server.clientsByUserName[self.name] = self
  378.             self.setState(self.STATE_POST_LOGIN)
  379.             self.setState(self.STATE_MAIN)
  380.         
  381.         elif state is self.STATE_PASSWORD:
  382.             if self.checkPassword(self.name, line):
  383.                 self.server.clientsByUserName[self.name] = self
  384.                 self.setState(self.STATE_POST_LOGIN)
  385.                 self.setState(self.STATE_MAIN)
  386.             else:
  387.                 self.setState(self.STATE_LOGIN)
  388.  
  389.         elif state is self.STATE_MAIN:
  390.             args = line.split(None, 1)
  391.             if len(args) > 0:
  392.                 command = args[0]
  393.                 if len(args) == 1:
  394.                     line = ''
  395.                 else:
  396.                     line = args[1]
  397.                 self.processCommand(command, line)
  398.             self.setState(self.STATE_MAIN)
  399.         
  400.     def processCommand(self, command, line):
  401.         args = line.split()
  402.         if command == 'quit' and len(args) == 0:
  403.             self.quit()
  404.  
  405.         elif command == 'sought':
  406.             for ad in self.server.adverts.itervalues():
  407.                 self.send(ad.genSoughtLine() + '\n')
  408.                  
  409.             if len(self.server.adverts) == 1:
  410.                 self.send('1 ad displayed.\n')
  411.             else:
  412.                 self.send('%i ads displayed.\n' % len(self.server.adverts))
  413.         
  414.         elif command == 'match' and len(args) == 1:
  415.             user = args[0]
  416.             try:
  417.                 client = self.server.clientsByUserName[user]
  418.             except KeyError:
  419.                 pass
  420.             else:
  421.                 advert = Challenge(self)
  422.                 advertLine = '?' # FIXME
  423.                 self.send('Issuing: %s.' % advertLine)
  424.                 client.send('\n')
  425.                 client.send('Challenge: %s.\n' % advertLine)
  426.                 client.send('You can "accept" or "decline", or propose different parameters.\n')
  427.                 client.sendPrompt()
  428.         
  429.         elif command == 'accept' and len(args) == 0:
  430.             pass
  431.             
  432.         elif command == 'decline' and len(args) == 0:
  433.             pass
  434.         
  435.         elif command == 'seek':
  436.             #Usage: seek [time inc] [rated|unrated] [white|black] [crazyhouse] [suicide]
  437.             #            [wild #] [auto|manual] [formula] [rating-range]
  438.             for arg in args:
  439.                 pass
  440.             
  441.             advert = self.server.addAdvert(self)
  442.             
  443.             # Notify all clients
  444.             notifyLine = '\n' + advert.genSeekMessage() + '\n'
  445.             for client in self.server.clients.itervalues():
  446.                 if client.name is None:
  447.                     continue
  448.                 client.send(notifyLine)
  449.                 client.sendPrompt()
  450.                 
  451.         elif command == 'play' and len(args) == 1:
  452.             try:
  453.                 advert = self.server.adverts[int(args[0])]
  454.             except ValueError:
  455.                 user = args[0]
  456.                 try:
  457.                     client = self.server.clientsByUserName[user]
  458.                 except KeyError:
  459.                     self.send('%s is not logged in.\n' % user)
  460.                     return
  461.                 else:
  462.                     advert = None # TODO: use first client.adverts
  463.             except KeyError:
  464.                 self.send('That seek is not available.\n')
  465.                 return
  466.             else:
  467.                 client = advert.client
  468.             
  469.             # Remove this advert
  470.             self.server.removeAdverts([advert])
  471.             
  472.             # Make a game
  473.             game = self.server.addGame(self, client)
  474.  
  475.         elif command == 'tell' or command == 't':
  476.             (user, text) = line.split(None, 1)
  477.             
  478.             if user.isalpha():
  479.                 # TODO: Could also be a channel
  480.                 try:
  481.                     client = self.server.clientsByUserName[user]
  482.                 except KeyError:
  483.                     self.send('%s is not logged in.\n' % user)
  484.                 else:
  485.                     self.send('(told %s)\n' % user)
  486.                     # FIXME: What it (U) ?
  487.                     client.send('\n')
  488.                     client.send('%s(U) tells you: %s\n' % (self.name, text))
  489.             else:
  490.                 self.send('%s is not a valid handle.\n' % repr(user))
  491.  
  492.             self.sendPrompt()
  493.                 
  494.         elif command == 'say':
  495.             text = line
  496.         
  497.         else:
  498.             self.send('%s: Command not found.\n' % command)
  499.  
  500. if __name__ == '__main__':
  501.  
  502.     class Connection:
  503.         def __init__(self, server, socket):
  504.             self.server = server
  505.             self.socket = socket
  506.             
  507.         def read(self):
  508.             (data, _) = self.socket.recvfrom(65535)
  509.             if len(data) == 0:
  510.                 self.abort()
  511.             else:
  512.                 print repr(data)
  513.                 self.server.registerIncomingData(self, data)
  514.  
  515.         def quit(self):
  516.             pass
  517.         
  518.         def abort(self):
  519.             self.server.fds.remove(self.socket.fileno())
  520.             self.server.connections.pop(self.socket.fileno())
  521.             self.socket.close()
  522.         
  523.     class S(Server):
  524.         def __init__(self):
  525.             self.connections = {}
  526.             self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  527.             try:
  528.                 self.socket.bind(('', 6666))
  529.             except socket.error:
  530.                 self.socket.bind(('', 6667))
  531.             self.socket.listen(10)
  532.             self.fds = [self.socket.fileno()]
  533.             Server.__init__(self)
  534.             
  535.         def onOutgoingData(self, connection, data):
  536.             connection.socket.send(data)
  537.         
  538.         def run(self):
  539.             import select
  540.             while True:
  541.                 print '!'
  542.                 (fds, _, _) = select.select(self.fds, [], [])
  543.                 print fds
  544.                 for fd in fds:
  545.                     if fd == self.socket.fileno():
  546.                         try:
  547.                             (s, _) = self.socket.accept()
  548.                         except socket.error, e:
  549.                             print e
  550.                         else:
  551.                             self.fds.append(s.fileno())
  552.                             c = Connection(self, s)
  553.                             self.connections[s.fileno()] = c
  554.                             self.addConnection(c)
  555.  
  556.                     else:
  557.                         c = self.connections[fd]
  558.                         c.read()
  559.  
  560.     s = S()
  561.     s.run()
  562.