OSDN Git Service

Refactoring. Pretty output.
[shogi-server/shogi-server.git] / shogi-server
index ae4c873..0cde3d2 100755 (executable)
@@ -17,6 +17,8 @@
 ## along with this program; if not, write to the Free Software
 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
+module ShogiServer # for a namespace
+
 Max_Write_Queue_Size = 1000
 Max_Identifier_Length = 32
 Default_Timeout = 60            # for single socket operation
@@ -31,9 +33,6 @@ Release = "$Name$".split[1].sub(/\A[^\d]*/, '').gsub(/_/, '.')
 Release.concat("-") if (Release == "")
 Revision = "$Revision$".gsub(/[^\.\d]/, '')
 
-STDOUT.sync = true
-STDERR.sync = true
-
 require 'getoptlong'
 require 'thread'
 require 'timeout'
@@ -42,8 +41,6 @@ require 'yaml'
 require 'yaml/store'
 require 'digest/md5'
 
-TCPSocket.do_not_reverse_lookup = true
-Thread.abort_on_exception = true
 
 
 class TCPSocket
@@ -117,31 +114,24 @@ class League
     return nil
   end
   
-  def save
-    @db.transaction do
-      @players.each_value do |p|
-        next unless p.id
-        @db[p.id] = {'name' => p.name, 
-                     'rate' => p.rate,
-                     'modified_at' => p.modified_at}
-      end
-    end
-  end
-
   def load(player)
     hash = search(player.id)
     if hash
       # a current user
-      player.name = hash['name']
-      player.rate = hash['rate']
-      player.modified_at = hash['modified_at']
+      player.name         = hash['name']
+      player.rate         = hash['rate']
+      player.modified_at  = hash['last_modified']
+      player.rating_group = hash['rating_group']
     end
   end
 
   def search(id)
     hash = nil
     @db.transaction do
-      hash = @db[id]
+      @db["players"].each do |group, players|
+        hash = players[id]
+        break if hash
+      end
     end
     hash
   end
@@ -149,12 +139,12 @@ class League
   def rated_players
     players = []
     @db.transaction(true) do
-      @db.roots.each do |id|
-        players << id
+      @db["players"].each do |group, players_hash|
+        players << players_hash.keys
       end
     end
-    return players.collect do |id|
-      p = RatedPlayer.new
+    return players.flatten.collect do |id|
+      p = BasicPlayer.new
       p.id = id
       self.load(p)
       p
@@ -162,18 +152,151 @@ class League
   end
 end
 
-#class RatedPlayer < Struct.new(:id, :name, :rate); end
 
-class RatedPlayer
+######################################################
+# Processes the LOGIN command.
+#
+class Login
+  def Login.good_login?(str)
+    tokens = str.split
+    if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
+        (tokens[0] == "LOGIN") &&
+        (good_identifier?(tokens[1])))
+      return true
+    else
+      return false
+    end
+  end
+
+  def Login.good_game_name?(str)
+    if ((str =~ /^(.+)-\d+-\d+$/) && (good_identifier?($1)))
+      return true
+    else
+      return false
+    end
+  end
+
+  def Login.good_identifier?(str)
+    if str =~ /\A[\w\d_@\-\.]{1,#{Max_Identifier_Length}}\z/
+      return true
+    else
+      return false
+    end
+  end
+
+  def Login.factory(str, player)
+    (login, player.name, password, ext) = str.chomp.split
+    if (ext)
+      return Loginx1.new(player, password)
+    else
+      return LoginCSA.new(player, password)
+    end
+  end
+
+  attr_reader :player
+  
+  # the first command that will be executed just after LOGIN.
+  # If it is nil, the default process will be started.
+  attr_reader :csa_1st_str
+
+  def initialize(player, password)
+    @player = player
+    @csa_1st_str = nil
+    parse_password(password)
+  end
+
+  def process
+    @player.write_safe(sprintf("LOGIN:%s OK\n", @player.name))
+    log_message(sprintf("user %s run in %s mode", @player.name, @player.protocol))
+  end
+
+  def incorrect_duplicated_player(str)
+    @player.write_safe("LOGIN:incorrect\n")
+    @player.write_safe(sprintf("username %s is already connected\n", @player.name)) if (str.split.length >= 4)
+    sleep 3 # wait for sending the above messages.
+    @player.name = "%s [duplicated]" % [@player.name]
+    @player.finish
+  end
+end
+
+######################################################
+# Processes LOGIN for the CSA standard mode.
+#
+class LoginCSA < Login
+  PROTOCOL = "CSA"
+
+  def initialize(player, password)
+    @gamename = nil
+    super
+    @player.protocol = PROTOCOL
+  end
+
+  def parse_password(password)
+    if Login.good_game_name?(password)
+      @gamename = password
+      @player.set_password(nil)
+    elsif password.split(",").size > 1
+      @gamename, *trip = password.split(",")
+      @player.set_password(trip.join(","))
+    else
+      @player.set_password(password)
+      @gamename = Default_Game_Name
+    end
+    @gamename = self.class.good_game_name?(@gamename) ? @gamename : Default_Game_Name
+  end
+
+  def process
+    super
+    @csa_1st_str = "%%GAME #{@gamename} *"
+  end
+end
+
+######################################################
+# Processes LOGIN for the extented mode.
+#
+class Loginx1 < Login
+  PROTOCOL = "x1"
+
+  def initialize(player, password)
+    super
+    @player.protocol = PROTOCOL
+  end
+  
+  def parse_password(password)
+    @player.set_password(password)
+  end
+
+  def process
+    super
+    @player.write_safe(sprintf("##[LOGIN] +OK %s\n", PROTOCOL))
+  end
+end
+
+
+class BasicPlayer
   # Idetifier of the player in the rating system
   attr_accessor :id
+
   # Name of the player
   attr_accessor :name
+  
+  # Password of the player, which does not include a trip
+  attr_accessor :password
+
   # Score in the rating sysem
   attr_accessor :rate
+  
+  # Group in the rating system
+  attr_accessor :rating_group
+
   # Last timestamp when the rate was modified
   attr_accessor :modified_at
 
+  def initialize
+    @name = nil
+    @password = nil
+  end
+
   def modified_at
     @modified_at || Time.now
   end
@@ -184,12 +307,37 @@ class RatedPlayer
       @modified_at = Time.now
     end
   end
+
+  def rated?
+    @id != nil
+  end
+
+  def simple_id
+    if @trip
+      simple_name = @name.gsub(/@.*?$/, '')
+      "%s+%s" % [simple_name, @trip[0..8]]
+    else
+      @name
+    end
+  end
+
+  ##
+  # Parses str in the LOGIN command, sets up @id and @trip
+  #
+  def set_password(str)
+    if str && !str.empty?
+      @password = str.strip
+      @id   = "%s+%s" % [@name, Digest::MD5.hexdigest(@password)]
+    else
+      @id = @password = nil
+    end
+  end
 end
 
-class Player < RatedPlayer
+
+class Player < BasicPlayer
   def initialize(str, socket)
-    @name = nil
-    @password = nil
+    super()
     @socket = socket
     @status = "connected"        # game_waiting -> agree_waiting -> start_waiting -> game -> finished
 
@@ -199,13 +347,15 @@ class Player < RatedPlayer
     @game_name = ""
     @mytime = 0                 # set in start method also
     @sente = nil
-    @writer_thread = nil
-    @main_thread = nil
     @write_queue = Queue::new
-    login(str)
+    @main_thread = Thread::current
+    @writer_thread = Thread::start do
+      Thread.pass
+      writer()
+    end
   end
 
-  attr_accessor :password, :socket, :status
+  attr_accessor :socket, :status
   attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente
   attr_accessor :main_thread, :writer_thread, :write_queue
   
@@ -222,6 +372,7 @@ class Player < RatedPlayer
     if (@status != "finished")
       @status = "finished"
       log_message(sprintf("user %s finish", @name))    
+      # TODO you should confirm that there is no message in the queue.
       Thread::kill(@writer_thread) if @writer_thread
       begin
         @socket.close if (! @socket.closed?)
@@ -262,38 +413,7 @@ class Player < RatedPlayer
     @socket.write_safe('##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"')
   end
 
-  def login(str)
-    str =~ /([\r\n]*)$/
-    @eol = $1
-    str.chomp!
-    (login, @name, @password, ext) = str.split
-    @name, trip = @name.split(",") # used by rating
-    @id = trip ? Digest::MD5.hexdigest("#{@name},#{@trip}") : nil
-    if (ext)
-      @protocol = "x1"
-    else
-      @protocol = "CSA"
-    end
-    @main_thread = Thread::current
-    @writer_thread = Thread::start do
-      writer()
-    end
-  end
-  
-  def run
-    write_safe(sprintf("LOGIN:%s OK\n", @name))
-    if (@protocol != "CSA")
-      log_message(sprintf("user %s run in %s mode", @name, @protocol))
-      write_safe(sprintf("##[LOGIN] +OK %s\n", @protocol))
-    else
-      log_message(sprintf("user %s run in CSA mode", @name))
-      if (good_game_name?(@password))
-        csa_1st_str = "%%GAME #{@password} *"
-      else
-        csa_1st_str = "%%GAME #{Default_Game_Name} *"
-      end
-    end
-    
+  def run(csa_1st_str=nil)
     while (csa_1st_str || (str = @socket.gets_safe(Default_Timeout)))
       begin
         $mutex.lock
@@ -321,17 +441,17 @@ class Player < RatedPlayer
             end
             s = @game.handle_one_move(move, self)
             @game.fh.print("#{comment}\n") if (comment && !s)
-            return if (s && @protocol == "CSA")
+            return if (s && @protocol == LoginCSA::PROTOCOL)
           end
         when /^%[^%]/, :timeout
           if (@status == "game")
             s = @game.handle_one_move(str, self)
-            return if (s && @protocol == "CSA")
+            return if (s && @protocol == LoginCSA::PROTOCOL)
           end
         when /^REJECT/
           if (@status == "agree_waiting")
             @game.reject(@name)
-            return if (@protocol == "CSA")
+            return if (@protocol == LoginCSA::PROTOCOL)
           else
             write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
           end
@@ -370,9 +490,13 @@ class Player < RatedPlayer
         when /^%%RATING/
           players = LEAGUE.rated_players
           players.sort {|a,b| b.rate <=> a.rate}.each do |p|
-            write_safe("%s (%s) \t %4d @%s\n" % 
-                       [p.name, p.id, p.rate, p.modified_at.strftime("%Y-%m-%d")])
+            write_safe("##[RATING] %s \t %4d @%s\n" % 
+                       [p.simple_id, p.rate, p.modified_at.strftime("%Y-%m-%d")])
           end
+          write_safe("##[RATING] +OK\n")
+        when /^%%VERSION/
+          write_safe "##[VERSION] Shogi Server revision #{Revision}\n"
+          write_safe("##[VERSION] +OK\n")
         when /^%%GAME\s*$/
           if ((@status == "connected") || (@status == "game_waiting"))
             @status = "connected"
@@ -384,7 +508,7 @@ class Player < RatedPlayer
           command_name = $1
           game_name = $2
           my_sente_str = $3
-          if (! good_game_name?(game_name))
+          if (! Login::good_game_name?(game_name))
             write_safe(sprintf("##[ERROR] bad game name\n"))
             next
           elsif ((@status == "connected") || (@status == "game_waiting"))
@@ -458,7 +582,7 @@ class Player < RatedPlayer
         when /^%%CHAT\s+(.+)/
           message = $1
           LEAGUE.players.each do |name, player|
-            if (player.protocol != "CSA")
+            if (player.protocol != LoginCSA::PROTOCOL)
               player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message)) 
             end
           end
@@ -1147,7 +1271,7 @@ class Board
     return true
   end
 
-  def handle_one_move(str, sente)
+  def handle_one_move(str, sente=nil)
     if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
       sg = $1
       x0 = $2.to_i
@@ -1156,6 +1280,7 @@ class Board
       y1 = $5.to_i
       name = $6
     elsif (str =~ /^%KACHI/)
+      raise ArgumentError, "sente is null", caller if sente == nil
       if (good_kachi?(sente))
         return :kachi_win
       else
@@ -1269,66 +1394,35 @@ class Board
   end
 end
 
-#################################################
-# Rating module
-#
-# http://www2.saganet.ne.jp/a-sim/mhp0726.html
-# http://www10.plala.or.jp/greenstone/content1_3.html
-class Rating
-  K = 16
-
-  def new_rate(me, you, win)
-    w = win ? 1 : 0
-    
-    if me == nil && you != nil
-      return (you + w*400).to_i
-    elsif me == nil && you == nil
-      return (1100 + w*400).to_i
-    elsif you == nil
-      return me.to_i
-    end
-    score = me + K*(w - we(me, you))
-    score.to_i
-  end
-
-  private
-
-  # win expectancy
-  def we(me, you)
-    dr = (me - you)
-    1.0 / ( 10**(-1.0*dr/400) + 1 )
-  end
-end
-
-
-
 class GameResult
+  attr_reader :players, :black, :white
+
   def initialize(p1, p2)
     @players = []
     @players << p1
     @players << p2
+    if p1.sente && !p2.sente
+      @black, @white = p1, p2
+    elsif !p1.sente && p2.sente
+      @black, @white = p2, p1
+    else
+      raise "Never reached!"
+    end
   end
 end
 
 class GameResultWin < GameResult
   attr_reader :winner, :loser
-    
+
   def initialize(winner, loser)
     super
     @winner, @loser = winner, loser
-    rate if @winner.id && @loser.id
-  end
-
-  def win?(player)
-    @winner == player
   end
 
-  def rate
-    rating = Rating.new
-    new_winner = rating.new_rate(@winner.rate, @loser.rate,  true)
-    new_loser  = rating.new_rate(@loser.rate,  @winner.rate, false)
-    @winner.rate = new_winner if new_winner
-    @loser.rate  = new_loser  if new_loser
+  def to_s
+    black_name = @black.id || @black.name
+    white_name = @white.id || @white.name
+    "%s:%s" % [black_name, white_name]
   end
 end
 
@@ -1387,6 +1481,10 @@ class Game
   attr_accessor :last_move, :current_turn
   attr_reader   :result
 
+  def rated?
+    @sente.rated? && @gote.rated?
+  end
+
   def monitoron(monitor)
     @monitors.delete(monitor)
     @monitors.push(monitor)
@@ -1421,10 +1519,10 @@ class Game
     @sente.status = "connected"
     @gote.status = "connected"
 
-    if (@current_player.protocol == "CSA")
+    if (@current_player.protocol == LoginCSA::PROTOCOL)
       @current_player.finish
     end
-    if (@next_player.protocol == "CSA")
+    if (@next_player.protocol == LoginCSA::PROTOCOL)
       @next_player.finish
     end
     @monitors = Array::new
@@ -1433,7 +1531,6 @@ class Game
     @current_player = nil
     @next_player = nil
     LEAGUE.games.delete(@id)
-    LEAGUE.save
   end
 
   def handle_one_move(str, player)
@@ -1516,10 +1613,11 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
     @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
-    @result = GameResultWin.new(@current_player, @next_player)
     @fh.printf("%%TORYO\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@current_player, @next_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
     end
@@ -1530,10 +1628,11 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
     @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
-    @result = GameResultWin.new(@next_player, @current_player)
     @fh.printf("%%TORYO\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@next_player, @current_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
     end
@@ -1544,9 +1643,10 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("#SENNICHITE\n#DRAW\n")
     @next_player.write_safe("#SENNICHITE\n#DRAW\n")
-    @result = GameResultDraw.new(@current_player, @next_player)
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
+    @result = GameResultDraw.new(@current_player, @next_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
     end
@@ -1557,9 +1657,10 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
     @next_player.write_safe("#OUTE_SENNICHITE\n#WIN\n")
-    @result = GameResultWin.new(@next_player, @current_player)
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@next_player, @current_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
     end
@@ -1570,9 +1671,10 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
-    @result = GameResultWin.new(@next_player, @current_player)
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@next_player, @current_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
     end
@@ -1583,9 +1685,10 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
-    @result = GameResultWin.new(@next_player, @current_player)
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@next_player, @current_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
     end
@@ -1596,9 +1699,10 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
-    @result = GameResultWin.new(@next_player, @current_player)
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@next_player, @current_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
     end
@@ -1609,9 +1713,10 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("#TIME_UP\n#LOSE\n")
     @next_player.write_safe("#TIME_UP\n#WIN\n")
-    @result = GameResultWin.new(@next_player, @current_player)
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@next_player, @current_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
     end
@@ -1622,10 +1727,11 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
     @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
-    @result = GameResultWin.new(@current_player, @next_player)
     @fh.printf("%%KACHI\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@current_player, @next_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
     end
@@ -1636,10 +1742,11 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n")
     @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n")
-    @result = GameResultWin(@next_player, @current_player)
     @fh.printf("%%KACHI\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@next_player, @current_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
     end
@@ -1650,10 +1757,11 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
     @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
-    @result = GameResultWin.new(@next_player, @current_player)
     @fh.printf("%%TORYO\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@next_player, @current_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
     end
@@ -1664,9 +1772,10 @@ class Game
     @next_player.status = "connected"
     @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
     @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
-    @result = GameResultWin.new(@current_player, @next_player)
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@current_player, @next_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
     end
@@ -1859,37 +1968,7 @@ def parse_command_line
   return options
 end
 
-def good_game_name?(str)
-  if ((str =~ /^(.+)-\d+-\d+$/) &&
-      (good_identifier?($1)))
-    return true
-  else
-    return false
-  end
-end
-
-def good_identifier?(str)
-  if ( ((str =~ /\A[\w\d_@\-\.]+\z/) || 
-        (str =~ /\A[\w\d_@\-\.]+,[\w\d_@\-\.]+\z/)) &&
-      (str.length < Max_Identifier_Length))
-    return true
-  else
-    return false
-  end
-end
-
-def good_login?(str)
-  tokens = str.split
-  if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
-      (tokens[0] == "LOGIN") &&
-      (good_identifier?(tokens[1])))
-    return true
-  else
-    return false
-  end
-end
-
-def  write_pid_file(file)
+def write_pid_file(file)
   open(file, "w") do |fh|
     fh.print Process::pid, "\n"
   end
@@ -1914,6 +1993,7 @@ def mutex_watchdog(mutex, sec)
 end
 
 def main
+
   $mutex = Mutex::new
   Thread::start do
     Thread.pass
@@ -1939,22 +2019,23 @@ def main
       Thread.pass
       client.sync = true
       player = nil
+      login  = nil
       while (str = client.gets_timeout(Login_Time))
         begin
           $mutex.lock
           str =~ /([\r\n]*)$/
           eol = $1
-          if (good_login?(str))
+          if (Login::good_login?(str))
             player = Player::new(str, client)
+            player.eol = eol
+            login  = Login::factory(str, player)
             if (LEAGUE.players[player.name])
               if ((LEAGUE.players[player.name].password == player.password) &&
                   (LEAGUE.players[player.name].status != "game"))
                 log_message(sprintf("user %s login forcely", player.name))
                 LEAGUE.players[player.name].kill
               else
-                client.write_safe("LOGIN:incorrect" + eol)
-                client.write_safe(sprintf("username %s is already connected%s", player.name, eol)) if (str.split.length >= 4)
-                client.close
+                login.incorrect_duplicated_player(str)
                 Thread::exit
                 return
               end
@@ -1975,7 +2056,8 @@ def main
         return
       end
       log_message(sprintf("user %s login", player.name))
-      player.run
+      login.process
+      player.run(login.csa_1st_str)
       begin
         $mutex.lock
         if (player.game)
@@ -1990,8 +2072,16 @@ def main
     end
   end
 end
+module_function :main
+
+end # module ShogiServer
 
 if ($0 == __FILE__)
-  LEAGUE = League::new
-  main
+  STDOUT.sync = true
+  STDERR.sync = true
+  TCPSocket.do_not_reverse_lookup = true
+  Thread.abort_on_exception = true
+  
+  LEAGUE = ShogiServer::League::new
+  ShogiServer::main
 end