OSDN Git Service

Improved a way to handle diferred moves (2008-03-24's change),
[shogi-server/shogi-server.git] / shogi-server
index 700106c..5d33d6f 100755 (executable)
@@ -2,6 +2,7 @@
 ## $Id$
 
 ## Copyright (C) 2004 NABEYA Kenichi (aka nanami@2ch)
 ## $Id$
 
 ## Copyright (C) 2004 NABEYA Kenichi (aka nanami@2ch)
+## Copyright (C) 2007-2008 Daigo Moriwaki (daigo at debian dot org)
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published by
 ##
 ## This program is free software; you can redistribute it and/or modify
 ## it under the terms of the GNU General Public License as published by
 ## along with this program; if not, write to the Free Software
 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 ## along with this program; if not, write to the Free Software
 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-Max_Write_Queue_Size = 1000
+require 'kconv'
+require 'getoptlong'
+require 'thread'
+require 'timeout'
+require 'socket'
+require 'yaml'
+require 'yaml/store'
+require 'digest/md5'
+require 'webrick'
+require 'fileutils'
+
+def gets_safe(socket, timeout=nil)
+  if r = select([socket], nil, nil, timeout)
+    return r[0].first.gets
+  else
+    return :timeout
+  end
+rescue Exception => ex
+  log_error("#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}")
+  return :exception
+end
+
+module ShogiServer # for a namespace
+
 Max_Identifier_Length = 32
 Default_Timeout = 60            # for single socket operation
 
 Max_Identifier_Length = 32
 Default_Timeout = 60            # for single socket operation
 
@@ -31,107 +55,422 @@ Release = "$Name$".split[1].sub(/\A[^\d]*/, '').gsub(/_/, '.')
 Release.concat("-") if (Release == "")
 Revision = "$Revision$".gsub(/[^\.\d]/, '')
 
 Release.concat("-") if (Release == "")
 Revision = "$Revision$".gsub(/[^\.\d]/, '')
 
-STDOUT.sync = true
-STDERR.sync = true
+class League
 
 
-require 'getoptlong'
-require 'thread'
-require 'timeout'
-require 'socket'
+  class Floodgate
+    class << self
+      def game_name?(str)
+        return /^floodgate-\d+-\d+$/.match(str) ? true : false
+      end
+    end
 
 
-TCPSocket.do_not_reverse_lookup = true
+    def initialize(league)
+      @league = league
+      @next_time = nil
+      charge
+    end
+
+    def run
+      @thread = Thread.new do
+        Thread.pass
+        while (true)
+          begin
+            sleep(10)
+            next if Time.now < @next_time
+            @league.reload
+            match_game
+            charge
+          rescue Exception => ex 
+            # ignore errors
+            log_error("[in Floodgate's thread] #{ex}")
+          end
+        end
+      end
+    end
 
 
+    def shutdown
+      @thread.kill if @thread
+    end
 
 
-class TCPSocket
-  def gets_timeout(t = Default_Timeout)
-    begin
-      timeout(t) do
-        return self.gets
+    # private
+
+    def charge
+      now = Time.now
+      if now.min < 30
+        @next_time = Time.mktime(now.year, now.month, now.day, now.hour, 30)
+      else
+        @next_time = Time.mktime(now.year, now.month, now.day, now.hour) + 3600
       end
       end
-    rescue TimeoutError
-      return nil
-    rescue
-      return nil
+      # for test
+      # if now.sec < 30
+      #   @next_time = Time.mktime(now.year, now.month, now.day, now.hour, now.min, 30)
+      # else
+      #   @next_time = Time.mktime(now.year, now.month, now.day, now.hour, now.min) + 60
+      # end
+    end
+
+    def match_game
+      players = @league.find_all_players do |pl|
+        pl.status == "game_waiting" &&
+        Floodgate.game_name?(pl.game_name) &&
+        pl.sente == nil
+      end
+      log_warning("DEBUG: %s" % [File.join(File.dirname(__FILE__), "pairing.rb")])
+      load File.join(File.dirname(__FILE__), "pairing.rb")
+      Pairing.default_pairing.match(players)
+    end
+  end # class Floodgate
+
+  def initialize
+    @mutex = Mutex.new # guard @players
+    @games = Hash::new
+    @players = Hash::new
+    @event = nil
+    @dir = File.dirname(__FILE__)
+    @floodgate = Floodgate.new(self)
+    @floodgate.run
+  end
+  attr_accessor :players, :games, :event, :dir
+
+  def shutdown
+    @mutex.synchronize do
+      @players.each {|a| save(a)}
     end
     end
+    @floodgate.shutdown
   end
   end
-  def gets_safe(t = nil)
-    if (t && t > 0)
-      begin
-        timeout(t) do
-          return self.gets
+
+  # this should be called just after instanciating a League object.
+  def setup_players_database
+    @db = YAML::Store.new(File.join(@dir, "players.yaml"))
+  end
+
+  def add(player)
+    self.load(player) if player.id
+    @mutex.synchronize do
+      @players[player.name] = player
+    end
+  end
+  
+  def delete(player)
+    @mutex.synchronize do
+      save(player)
+      @players.delete(player.name)
+    end
+  end
+
+  def reload
+    @mutex.synchronize do
+      @players.each {|player| load(player)}
+    end
+  end
+
+  def find_all_players
+    found = nil
+    @mutex.synchronize do
+      found = @players.find_all do |name, player|
+        yield player
+      end
+    end
+    return found.map {|a| a.last}
+  end
+  
+  def find(player_name)
+    found = nil
+    @mutex.synchronize do
+      found = @players[player_name]
+    end
+    return found
+  end
+
+  def get_player(status, game_name, sente, searcher)
+    found = nil
+    @mutex.synchronize do
+      found = @players.find do |name, player|
+        (player.status == status) &&
+        (player.game_name == game_name) &&
+        ( (sente == nil) || 
+          (player.sente == nil) || 
+          (player.sente == sente) ) &&
+        (player.name != searcher.name)
+      end
+    end
+    return found ? found.last : nil
+  end
+  
+  def load(player)
+    hash = search(player.id)
+    return unless hash
+
+    # a current user
+    player.name          = hash['name']
+    player.rate          = hash['rate'] || 0
+    player.modified_at   = hash['last_modified']
+    player.rating_group  = hash['rating_group']
+    player.win           = hash['win']  || 0
+    player.loss          = hash['loss'] || 0
+    player.last_game_win = hash['last_game_win'] || false
+  end
+
+  def save(player)
+    @db.transaction do
+      break unless  @db["players"]
+      @db["players"].each do |group, players|
+        hash = players[player.id]
+        if hash
+          hash['last_game_win'] = player.last_game_win
+          break
         end
         end
-      rescue TimeoutError
-        return :timeout
-      rescue
-        return nil
       end
       end
-    else
-      begin
-        return self.gets
-      rescue
-        return nil
+    end
+  end
+
+  def search(id)
+    hash = nil
+    @db.transaction(true) do
+      break unless  @db["players"]
+      @db["players"].each do |group, players|
+        hash = players[id]
+        break if hash
       end
     end
       end
     end
+    hash
   end
   end
-  def write_safe(str)
-    begin
-      return self.write(str)
-    rescue
-      return nil
+
+  def rated_players
+    players = []
+    @db.transaction(true) do
+      break unless  @db["players"]
+      @db["players"].each do |group, players_hash|
+        players << players_hash.keys
+      end
+    end
+    return players.flatten.collect do |id|
+      p = BasicPlayer.new
+      p.id = id
+      self.load(p)
+      p
     end
   end
 end
 
 
     end
   end
 end
 
 
-class League
-  def initialize
-    @games = Hash::new
-    @players = Hash::new
-    @event = nil
+######################################################
+# 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
   end
-  attr_accessor :players, :games, :event
 
 
-  def add(player)
-    @players[player.name] = player
+  def Login.good_game_name?(str)
+    if ((str =~ /^(.+)-\d+-\d+$/) && (good_identifier?($1)))
+      return true
+    else
+      return false
+    end
   end
   end
-  def delete(player)
-    @players.delete(player.name)
-  end
-  def get_player(status, game_name, sente, searcher=nil)
-    @players.each do |name, player|
-      if ((player.status == status) &&
-          (player.game_name == game_name) &&
-          ((player.sente == nil) || (player.sente == sente)) &&
-          ((searcher == nil) || (player != searcher)))
-        return player
-      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
-    return nil
+  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
 
   end
 end
 
-class Player
-  def initialize(str, socket)
+######################################################
+# 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
+  def initialize
+    @id = nil
     @name = nil
     @password = nil
     @name = nil
     @password = nil
+    @last_game_win = false
+  end
+
+  # 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
+
+  # Number of games for win and loss in the rating system
+  attr_accessor :win, :loss
+  
+  # Group in the rating system
+  attr_accessor :rating_group
+
+  # Last timestamp when the rate was modified
+  attr_accessor :modified_at
+
+  # Whether win the previous game or not
+  attr_accessor :last_game_win
+
+  def modified_at
+    @modified_at || Time.now
+  end
+
+  def rate=(new_rate)
+    if @rate != new_rate
+      @rate = new_rate
+      @modified_at = Time.now
+    end
+  end
+
+  def rated?
+    @id != nil
+  end
+
+  def last_game_win?
+    return @last_game_win
+  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 < BasicPlayer
+  def initialize(str, socket, eol=nil)
+    super()
     @socket = socket
     @socket = socket
-    @status = "connected"        # game_waiting -> agree_waiting -> start_waiting -> game -> finished
+    @status = "connected"       # game_waiting -> agree_waiting -> start_waiting -> game -> finished
 
     @protocol = nil             # CSA or x1
 
     @protocol = nil             # CSA or x1
-    @eol = "\m"                 # favorite eol code
+    @eol = eol || "\m"          # favorite eol code
     @game = nil
     @game_name = ""
     @mytime = 0                 # set in start method also
     @sente = nil
     @game = nil
     @game_name = ""
     @mytime = 0                 # set in start method also
     @sente = nil
-    @writer_thread = nil
-    @main_thread = nil
-    @write_queue = Queue::new
-    login(str)
+    @socket_buffer = []
+    @main_thread = Thread::current
+    @mutex_write_guard = Mutex.new
   end
 
   end
 
-  attr_accessor :name, :password, :socket, :status
+  attr_accessor :socket, :status
   attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente
   attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente
-  attr_accessor :main_thread, :writer_thread, :write_queue
+  attr_accessor :main_thread
+  attr_reader :socket_buffer
+  
   def kill
     log_message(sprintf("user %s killed", @name))
     if (@game)
   def kill
     log_message(sprintf("user %s killed", @name))
     if (@game)
@@ -145,9 +484,8 @@ class Player
     if (@status != "finished")
       @status = "finished"
       log_message(sprintf("user %s finish", @name))    
     if (@status != "finished")
       @status = "finished"
       log_message(sprintf("user %s finish", @name))    
-      Thread::kill(@writer_thread) if @writer_thread
       begin
       begin
-        @socket.close if (! @socket.closed?)
+#        @socket.close if (! @socket.closed?)
       rescue
         log_message(sprintf("user %s finish failed", @name))    
       end
       rescue
         log_message(sprintf("user %s finish failed", @name))    
       end
@@ -155,88 +493,95 @@ class Player
   end
 
   def write_safe(str)
   end
 
   def write_safe(str)
-    @write_queue.push(str.gsub(/[\r\n]+/, @eol))
-  end
-
-  def writer
-    while (str = @write_queue.pop)
-      @socket.write_safe(str)
+    @mutex_write_guard.synchronize do
+      begin
+        if @socket.closed?
+          log_warning("%s's socket has been closed." % [@name])
+          return
+        end
+        if r = select(nil, [@socket], nil, 20)
+          r[1].first.write(str)
+        else
+          log_error("Sending a message to #{@name} timed up.")
+        end
+      rescue Exception => ex
+        log_error("Failed to send a message to #{@name}. #{ex.class}: #{ex.message}\t#{ex.backtrace[0]}")
+      end
     end
   end
 
   def to_s
     end
   end
 
   def to_s
-    if ((status == "game_waiting") ||
-        (status == "start_waiting") ||
-        (status == "agree_waiting") ||
-        (status == "game"))
+    if ["game_waiting", "start_waiting", "agree_waiting", "game"].include?(status)
       if (@sente)
         return sprintf("%s %s %s %s +", @name, @protocol, @status, @game_name)
       elsif (@sente == false)
         return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name)
       elsif (@sente == nil)
       if (@sente)
         return sprintf("%s %s %s %s +", @name, @protocol, @status, @game_name)
       elsif (@sente == false)
         return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name)
       elsif (@sente == nil)
-        return sprintf("%s %s %s %s", @name, @protocol, @status, @game_name)
+        return sprintf("%s %s %s %s *", @name, @protocol, @status, @game_name)
       end
     else
       return sprintf("%s %s %s", @name, @protocol, @status)
     end
   end
 
       end
     else
       return sprintf("%s %s %s", @name, @protocol, @status)
     end
   end
 
-  def write_help
-    @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
-    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))
-      csa_1st_str = "%%GAME #{Default_Game_Name}"
-    end
-    
-    while (csa_1st_str || (str = @socket.gets_safe(Default_Timeout)))
+  def run(csa_1st_str=nil)
+    while ( csa_1st_str || 
+            str = gets_safe(@socket, (@socket_buffer.empty? ? Default_Timeout : 1)) )
+      $mutex.lock
       begin
       begin
-        $mutex.lock
+        if (@game && @game.turn?(self))
+          @socket_buffer << str
+          str = @socket_buffer.shift
+        end
+        log_message("%s (%s)" % [str, @socket_buffer.map {|a| String === a ? a.strip : a }.join(",")]) if $DEBUG
+
         if (csa_1st_str)
           str = csa_1st_str
           csa_1st_str = nil
         end
         if (csa_1st_str)
           str = csa_1st_str
           csa_1st_str = nil
         end
-        if (@write_queue.size > Max_Write_Queue_Size)
-          log_warning(sprintf("write_queue of %s is %d", @name, @write_queue.size))
-         return
-        end
 
         if (@status == "finished")
           return
         end
 
         if (@status == "finished")
           return
         end
-        str.chomp! if (str.class == String)
-        case str
-        when /^[\+\-%][^%]/, :timeout
+        str.chomp! if (str.class == String) # may be strip! ?
+        case str 
+        when "" 
+          # Application-level protocol for Keep-Alive
+          # If the server gets LF, it sends back LF.
+          # 30 sec rule (client may not send LF again within 30 sec) is not implemented yet.
+          write_safe("\n")
+        when /^[\+\-][^%]/
+          if (@status == "game")
+            array_str = str.split(",")
+            move = array_str.shift
+            additional = array_str.shift
+            if /^'(.*)/ =~ additional
+              comment = array_str.unshift("'*#{$1.toeuc}")
+            end
+            s = @game.handle_one_move(move, self)
+            @game.fh.print("#{Kconv.toeuc(comment.first)}\n") if (comment && comment.first && !s)
+            return if (s && @protocol == LoginCSA::PROTOCOL)
+          end
+        when /^%[^%]/, :timeout
           if (@status == "game")
             s = @game.handle_one_move(str, self)
           if (@status == "game")
             s = @game.handle_one_move(str, self)
-            return if (s && @protocol == "CSA")
+            return if (s && @protocol == LoginCSA::PROTOCOL)
+          # else
+          #   begin
+          #     @socket.write("##[KEEPALIVE] #{Time.now}\n")
+          #   rescue Exception => ex
+          #     log_error("Failed to send a keepalive to #{@name}.")
+          #     log_error("#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}")
+          #     return
+          #   end
           end
           end
+        when :exception
+          log_error("Failed to receive a message from #{@name}.")
+          return
         when /^REJECT/
           if (@status == "agree_waiting")
             @game.reject(@name)
         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
           else
             write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
           end
@@ -271,15 +616,30 @@ class Player
             LEAGUE.games[game_id].monitoroff(self)
           end
         when /^%%HELP/
             LEAGUE.games[game_id].monitoroff(self)
           end
         when /^%%HELP/
-          write_help
+          write_safe(
+            %!##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"\n!)
+        when /^%%RATING/
+          players = LEAGUE.rated_players
+          players.sort {|a,b| b.rate <=> a.rate}.each do |p|
+            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*$/
         when /^%%GAME\s*$/
-          @status = "connected"
-          @game_name = ""
-        when /^%%(GAME|CHALLENGE)\s+(\S+)\s*([\+\-]*)\s*$/
+          if ((@status == "connected") || (@status == "game_waiting"))
+            @status = "connected"
+            @game_name = ""
+          else
+            write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
+          end
+        when /^%%(GAME|CHALLENGE)\s+(\S+)\s+([\+\-\*])\s*$/
           command_name = $1
           game_name = $2
           my_sente_str = $3
           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"))
             write_safe(sprintf("##[ERROR] bad game name\n"))
             next
           elsif ((@status == "connected") || (@status == "game_waiting"))
@@ -288,27 +648,31 @@ class Player
             write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
             next
           end
             write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
             next
           end
-          if ((my_sente_str == "") ||
-              (my_sente_str == "+") ||
-              (my_sente_str == "-"))
-            ## ok
-          else
-            write_safe(sprintf("##[ERROR] bad game option\n"))
-            next
-          end
 
 
-          if (my_sente_str == "")
-            rival = LEAGUE.get_player("game_waiting", game_name, nil, self) # no preference
-          elsif (my_sente_str == "+")
-            rival = LEAGUE.get_player("game_waiting", game_name, false, self) # rival must be gote
-          elsif (my_sente_str == "-")
-            rival = LEAGUE.get_player("game_waiting", game_name, true, self) # rival must be sente
+          rival = nil
+          if (League::Floodgate.game_name?(game_name))
+            if (my_sente_str != "*")
+              write_safe(sprintf("##[ERROR] You are not allowed to specify TEBAN %s for the game %s\n", my_sente_str, game_name))
+              next
+            end
+            @sente = nil
           else
           else
-            ## never reached
+            if (my_sente_str == "*")
+              rival = LEAGUE.get_player("game_waiting", game_name, nil, self) # no preference
+            elsif (my_sente_str == "+")
+              rival = LEAGUE.get_player("game_waiting", game_name, false, self) # rival must be gote
+            elsif (my_sente_str == "-")
+              rival = LEAGUE.get_player("game_waiting", game_name, true, self) # rival must be sente
+            else
+              ## never reached
+              write_safe(sprintf("##[ERROR] bad game option\n"))
+              next
+            end
           end
           end
+
           if (rival)
             @game_name = game_name
           if (rival)
             @game_name = game_name
-            if ((my_sente_str == "") && (rival.sente == nil))
+            if ((my_sente_str == "*") && (rival.sente == nil))
               if (rand(2) == 0)
                 @sente = true
                 rival.sente = false
               if (rand(2) == 0)
                 @sente = true
                 rival.sente = false
@@ -330,8 +694,6 @@ class Player
               ## never reached
             end
             Game::new(@game_name, self, rival)
               ## never reached
             end
             Game::new(@game_name, self, rival)
-            self.status = "agree_waiting"
-            rival.status = "agree_waiting"
           else # rival not found
             if (command_name == "GAME")
               @status = "game_waiting"
           else # rival not found
             if (command_name == "GAME")
               @status = "game_waiting"
@@ -353,9 +715,8 @@ class Player
         when /^%%CHAT\s+(.+)/
           message = $1
           LEAGUE.players.each do |name, player|
         when /^%%CHAT\s+(.+)/
           message = $1
           LEAGUE.players.each do |name, player|
-            if (player.protocol != "CSA")
-              s = player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message)) 
-              player.status = "zombie" if (! s)
+            if (player.protocol != LoginCSA::PROTOCOL)
+              player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message)) 
             end
           end
         when /^%%LIST/
             end
           end
         when /^%%LIST/
@@ -376,20 +737,27 @@ class Player
           @status = "connected"
           write_safe("LOGOUT:completed\n")
           return
           @status = "connected"
           write_safe("LOGOUT:completed\n")
           return
+        when /^CHALLENGE/
+          # This command is only available for CSA's official testing server.
+          # So, this means nothing for this program.
+          write_safe("CHALLENGE ACCEPTED\n")
         when /^\s*$/
           ## ignore null string
         else
         when /^\s*$/
           ## ignore null string
         else
-          write_safe(sprintf("##[ERROR] unknown command %s\n", str))
+          msg = "##[ERROR] unknown command %s\n" % [str]
+          write_safe(msg)
+          log_error(msg)
         end
       ensure
         $mutex.unlock
       end
         end
       ensure
         $mutex.unlock
       end
-    end                         # enf of while
-  end
-end
+    end # enf of while
+  end # def run
+end # class
 
 class Piece
 
 class Piece
-  PROMOTE = {"FU" => "TO", "KY" => "NY", "KE" => "NK", "GI" => "NG", "KA" => "UM", "HI" => "RY"}
+  PROMOTE = {"FU" => "TO", "KY" => "NY", "KE" => "NK", 
+             "GI" => "NG", "KA" => "UM", "HI" => "RY"}
   def initialize(board, x, y, sente, promoted=false)
     @board = board
     @x = x
   def initialize(board, x, y, sente, promoted=false)
     @board = board
     @x = x
@@ -766,13 +1134,16 @@ end
 class Board
   def initialize
     @sente_hands = Array::new
 class Board
   def initialize
     @sente_hands = Array::new
-    @gote_hands = Array::new
-    @history = Hash::new
-    @sente_history = Hash::new
-    @gote_history = Hash::new
+    @gote_hands  = Array::new
+    @history       = Hash::new(0)
+    @sente_history = Hash::new(0)
+    @gote_history  = Hash::new(0)
     @array = [[], [], [], [], [], [], [], [], [], []]
     @array = [[], [], [], [], [], [], [], [], [], []]
+    @move_count = 0
+    @teban = nil # black => true, white => false
   end
   attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
   end
   attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
+  attr_reader :move_count
 
   def initial
     PieceKY::new(self, 1, 1, false)
 
   def initial
     PieceKY::new(self, 1, 1, false)
@@ -786,15 +1157,9 @@ class Board
     PieceKY::new(self, 9, 1, false)
     PieceKA::new(self, 2, 2, false)
     PieceHI::new(self, 8, 2, false)
     PieceKY::new(self, 9, 1, false)
     PieceKA::new(self, 2, 2, false)
     PieceHI::new(self, 8, 2, false)
-    PieceFU::new(self, 1, 3, false)
-    PieceFU::new(self, 2, 3, false)
-    PieceFU::new(self, 3, 3, false)
-    PieceFU::new(self, 4, 3, false)
-    PieceFU::new(self, 5, 3, false)
-    PieceFU::new(self, 6, 3, false)
-    PieceFU::new(self, 7, 3, false)
-    PieceFU::new(self, 8, 3, false)
-    PieceFU::new(self, 9, 3, false)
+    (1..9).each do |i|
+      PieceFU::new(self, i, 3, false)
+    end
 
     PieceKY::new(self, 1, 9, true)
     PieceKE::new(self, 2, 9, true)
 
     PieceKY::new(self, 1, 9, true)
     PieceKE::new(self, 2, 9, true)
@@ -807,15 +1172,10 @@ class Board
     PieceKY::new(self, 9, 9, true)
     PieceKA::new(self, 8, 8, true)
     PieceHI::new(self, 2, 8, true)
     PieceKY::new(self, 9, 9, true)
     PieceKA::new(self, 8, 8, true)
     PieceHI::new(self, 2, 8, true)
-    PieceFU::new(self, 1, 7, true)
-    PieceFU::new(self, 2, 7, true)
-    PieceFU::new(self, 3, 7, true)
-    PieceFU::new(self, 4, 7, true)
-    PieceFU::new(self, 5, 7, true)
-    PieceFU::new(self, 6, 7, true)
-    PieceFU::new(self, 7, 7, true)
-    PieceFU::new(self, 8, 7, true)
-    PieceFU::new(self, 9, 7, true)
+    (1..9).each do |i|
+      PieceFU::new(self, i, 7, true)
+    end
+    @teban = true
   end
 
   def have_piece?(hands, name)
   end
 
   def have_piece?(hands, name)
@@ -834,25 +1194,27 @@ class Board
 
     if ((x0 == 0) || (y0 == 0))
       piece = have_piece?(hands, name)
 
     if ((x0 == 0) || (y0 == 0))
       piece = have_piece?(hands, name)
-      return :illegal if (! piece.move_to?(x1, y1, name))
+      return :illegal if (! piece.move_to?(x1, y1, name)) # TODO null check for the piece?
       piece.move_to(x1, y1)
     else
       piece.move_to(x1, y1)
     else
-      return :illegal if (! @array[x0][y0].move_to?(x1, y1, name))
+      return :illegal if (! @array[x0][y0].move_to?(x1, y1, name))  # TODO null check?
       if (@array[x0][y0].name != name) # promoted ?
         @array[x0][y0].promoted = true
       end
       if (@array[x0][y0].name != name) # promoted ?
         @array[x0][y0].promoted = true
       end
-      if (@array[x1][y1])
+      if (@array[x1][y1]) # capture
         if (@array[x1][y1].name == "OU")
           return :outori        # return board update
         end
         @array[x1][y1].sente = @array[x0][y0].sente
         @array[x1][y1].move_to(0, 0)
         if (@array[x1][y1].name == "OU")
           return :outori        # return board update
         end
         @array[x1][y1].sente = @array[x0][y0].sente
         @array[x1][y1].move_to(0, 0)
-        hands.sort! {|a, b|
+        hands.sort! {|a, b| # TODO refactor. Move to Piece class
           a.name <=> b.name
         }
       end
       @array[x0][y0].move_to(x1, y1)
     end
           a.name <=> b.name
         }
       end
       @array[x0][y0].move_to(x1, y1)
     end
+    @move_count += 1
+    @teban = @teban ? false : true
     return true
   end
 
     return true
   end
 
@@ -873,6 +1235,7 @@ class Board
     raise "can't find ou"
   end
 
     raise "can't find ou"
   end
 
+  # note checkmate, but check. sente is checked.
   def checkmated?(sente)        # sente is loosing
     ou = look_for_ou(sente)
     x = 1
   def checkmated?(sente)        # sente is loosing
     ou = look_for_ou(sente)
     x = 1
@@ -905,7 +1268,7 @@ class Board
         return false
       end
     else                        # gote
         return false
       end
     else                        # gote
-      if ((rival_ou.y != 0) &&
+      if ((rival_ou.y != 1) &&
           (@array[rival_ou.x][rival_ou.y - 1]) &&
           (@array[rival_ou.x][rival_ou.y - 1].name == "FU") &&
           (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true
           (@array[rival_ou.x][rival_ou.y - 1]) &&
           (@array[rival_ou.x][rival_ou.y - 1].name == "FU") &&
           (@array[rival_ou.x][rival_ou.y - 1].sente == sente)) # uchifu true
@@ -915,9 +1278,8 @@ class Board
         return false
       end
     end
         return false
       end
     end
-    
+
     ## case: rival_ou is moving
     ## case: rival_ou is moving
-    escaped = false
     rival_ou.movable_grids.each do |(cand_x, cand_y)|
       tmp_board = Marshal.load(Marshal.dump(self))
       s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente)
     rival_ou.movable_grids.each do |(cand_x, cand_y)|
       tmp_board = Marshal.load(Marshal.dump(self))
       s = tmp_board.move_to(rival_ou.x, rival_ou.y, cand_x, cand_y, "OU", ! sente)
@@ -935,17 +1297,30 @@ class Board
         if (@array[x][y] &&
             (@array[x][y].sente != sente) &&
             @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
         if (@array[x][y] &&
             (@array[x][y].sente != sente) &&
             @array[x][y].movable_grids.include?([fu_x, fu_y])) # capturable
+          
+          names = []
           if (@array[x][y].promoted)
           if (@array[x][y].promoted)
-            name = @array[x][y].promoted_name
+            names << @array[x][y].promoted_name
           else
           else
-            name = @array[x][y].name
+            names << @array[x][y].name
+            if @array[x][y].promoted_name && 
+               @array[x][y].move_to?(fu_x, fu_y, @array[x][y].promoted_name)
+              names << @array[x][y].promoted_name 
+            end
           end
           end
-          tmp_board = Marshal.load(Marshal.dump(self))
-          s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
-          raise "internal error" if (s != true)
-          if (! tmp_board.checkmated?(! sente)) # good move
-            return false
+          names.map! do |name|
+            tmp_board = Marshal.load(Marshal.dump(self))
+            s = tmp_board.move_to(x, y, fu_x, fu_y, name, ! sente)
+            if s == :illegal
+              s # result
+            else
+              tmp_board.checkmated?(! sente) # result
+            end
           end
           end
+          all_illegal = names.find {|a| a != :illegal}
+          raise "internal error: legal move not found" if all_illegal == nil
+          r = names.find {|a| a == false} # good move
+          return false if r == false # found good move
         end
         y = y + 1
       end
         end
         y = y + 1
       end
@@ -954,36 +1329,65 @@ class Board
     return true
   end
 
     return true
   end
 
-  def oute_sennichite?(sente)
-    if (checkmated?(! sente))
-      str = to_s
-      if (sente)
-        if (@sente_history[str] && (@sente_history[str] >= 3)) # already 3 times
-          return true
-        end
+  # @[sente|gote]_history has at least one item while the player is checking the other or 
+  # the other escapes.
+  def update_sennichite(player)
+    str = to_s
+    @history[str] += 1
+    if checkmated?(!player)
+      if (player)
+        @sente_history["dummy"] = 1  # flag to see Sente player is checking Gote player
       else
       else
-        if (@gote_history[str] && (@gote_history[str] >= 3)) # already 3 times
-          return true
-        end
+        @gote_history["dummy"]  = 1  # flag to see Gote player is checking Sente player
+      end
+    else
+      if (player)
+        @sente_history.clear # no more continuous check
+      else
+        @gote_history.clear  # no more continuous check
       end
     end
       end
     end
-    return false
+    if @sente_history.size > 0  # possible for Sente's or Gote's turn
+      @sente_history[str] += 1
+    end
+    if @gote_history.size > 0   # possible for Sente's or Gote's turn
+      @gote_history[str] += 1
+    end
+  end
+
+  def oute_sennichite?(player)
+    if (@sente_history[to_s] >= 4)
+      return :oute_sennichite_sente_lose
+    elsif (@gote_history[to_s] >= 4)
+      return :oute_sennichite_gote_lose
+    else
+      return nil
+    end
   end
 
   def sennichite?(sente)
   end
 
   def sennichite?(sente)
-    str = to_s
-    if (@history[str] && (@history[str] >= 3)) # already 3 times
+    if (@history[to_s] >= 4) # already 3 times
       return true
     end
     return false
   end
 
   def good_kachi?(sente)
       return true
     end
     return false
   end
 
   def good_kachi?(sente)
-    return false if (checkmated?(sente))
+    if (checkmated?(sente))
+      puts "'NG: Checkmating." if $DEBUG
+      return false 
+    end
+    
     ou = look_for_ou(sente)
     ou = look_for_ou(sente)
-    return false if (sente && (ou.y >= 4))
-    return false if (! sente && (ou.y <= 6))
-
+    if (sente && (ou.y >= 4))
+      puts "'NG: Black's OU does not enter yet." if $DEBUG
+      return false     
+    end  
+    if (! sente && (ou.y <= 6))
+      puts "'NG: White's OU does not enter yet." if $DEBUG
+      return false 
+    end
+      
     number = 0
     point = 0
 
     number = 0
     point = 0
 
@@ -1010,16 +1414,28 @@ class Board
       point = point + piece.point
     end
 
       point = point + piece.point
     end
 
-    return false if (number < 10)
+    if (number < 10)
+      puts "'NG: Piece#[%d] is too small." % [number] if $DEBUG
+      return false     
+    end  
     if (sente)
     if (sente)
-      return false if (point < 28)
+      if (point < 28)
+        puts "'NG: Black's point#[%d] is too small." % [point] if $DEBUG
+        return false 
+      end  
     else
     else
-      return false if (point < 27)
+      if (point < 27)
+        puts "'NG: White's point#[%d] is too small." % [point] if $DEBUG
+        return false 
+      end
     end
     end
+
+    puts "'Good: Piece#[%d], Point[%d]." % [number, point] if $DEBUG
     return true
   end
 
     return true
   end
 
-  def handle_one_move(str)
+  # sente is nil only if tests in test_board run
+  def handle_one_move(str, sente=nil)
     if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
       sg = $1
       x0 = $2.to_i
     if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
       sg = $1
       x0 = $2.to_i
@@ -1028,11 +1444,7 @@ class Board
       y1 = $5.to_i
       name = $6
     elsif (str =~ /^%KACHI/)
       y1 = $5.to_i
       name = $6
     elsif (str =~ /^%KACHI/)
-      if (@sente == @current_player)
-        sente = true
-      else
-        sente = false
-      end
+      raise ArgumentError, "sente is null", caller if sente == nil
       if (good_kachi?(sente))
         return :kachi_win
       else
       if (good_kachi?(sente))
         return :kachi_win
       else
@@ -1052,10 +1464,12 @@ class Board
     end
     
     if (sg == "+")
     end
     
     if (sg == "+")
-      sente = true
+      sente = true if sente == nil           # deprecated
+      return :illegal unless sente == true   # black player's move must be black
       hands = @sente_hands
     else
       hands = @sente_hands
     else
-      sente = false
+      sente = false if sente == nil          # deprecated
+      return :illegal unless sente == false  # white player's move must be white
       hands = @gote_hands
     end
     
       hands = @gote_hands
     end
     
@@ -1081,7 +1495,9 @@ class Board
     tmp_board = Marshal.load(Marshal.dump(self))
     return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
     return :oute_kaihimore if (tmp_board.checkmated?(sente))
     tmp_board = Marshal.load(Marshal.dump(self))
     return :illegal if (tmp_board.move_to(x0, y0, x1, y1, name, sente) == :illegal)
     return :oute_kaihimore if (tmp_board.checkmated?(sente))
-    return :oute_sennichite if tmp_board.oute_sennichite?(sente)
+    tmp_board.update_sennichite(sente)
+    os_result = tmp_board.oute_sennichite?(sente)
+    return os_result if os_result # :oute_sennichite_sente_lose or :oute_sennichite_gote_lose
     return :sennichite if tmp_board.sennichite?(sente)
 
     if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
     return :sennichite if tmp_board.sennichite?(sente)
 
     if ((x0 == 0) && (y0 == 0) && (name == "FU") && tmp_board.uchifuzume?(sente))
@@ -1089,22 +1505,8 @@ class Board
     end
 
     move_to(x0, y0, x1, y1, name, sente)
     end
 
     move_to(x0, y0, x1, y1, name, sente)
-    str = to_s
 
 
-    if (checkmated?(! sente))
-      if (sente)
-        @sente_history[str] = (@sente_history[str] || 0) + 1
-      else
-        @gote_history[str] = (@gote_history[str] || 0) + 1
-      end
-    else
-      if (sente)
-        @sente_history.clear
-      else
-        @gote_history.clear
-      end
-    end
-    @history[str] = (@history[str] || 0) + 1
+    update_sennichite(sente)
     return :normal
   end
 
     return :normal
   end
 
@@ -1141,12 +1543,55 @@ class Board
       end
       a.push("\n")
     end
       end
       a.push("\n")
     end
-    a.push("+\n")
+    a.push("%s\n" % [@teban ? "+" : "-"])
     return a.join
   end
 end
 
     return a.join
   end
 end
 
+class GameResult
+  attr_reader :players, :black, :white
+
+  def initialize(p1, p2)
+    @players = [p1, 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
+    @winner.last_game_win = true
+    @loser.last_game_win  = false
+  end
+
+  def to_s
+    black_name = @black.id || @black.name
+    white_name = @white.id || @white.name
+    "%s:%s" % [black_name, white_name]
+  end
+end
+
+class GameResultDraw < GameResult
+  def initialize(p1, p2)
+    super
+    p1.last_game_win = false
+    p2.last_game_win = false
+  end
+end
+
 class Game
 class Game
+  @@mutex = Mutex.new
+  @@time  = 0
+
   def initialize(game_name, player0, player1)
     @monitors = Array::new
     @game_name = game_name
   def initialize(game_name, player0, player1)
     @monitors = Array::new
     @game_name = game_name
@@ -1156,42 +1601,50 @@ class Game
     end
 
     if (player0.sente)
     end
 
     if (player0.sente)
-      @sente = player0
-      @gote = player1
+      @sente, @gote = player0, player1
     else
     else
-      @sente = player1
-      @gote = player0
+      @sente, @gote = player1, player0
     end
     end
-    @current_player = @sente
-    @next_player = @gote
-
+    @sente.socket_buffer.clear
+    @gote.socket_buffer.clear
+    @current_player, @next_player = @sente, @gote
     @sente.game = self
     @sente.game = self
-    @gote.game = self
+    @gote.game  = self
 
     @last_move = ""
     @current_turn = 0
 
     @sente.status = "agree_waiting"
 
     @last_move = ""
     @current_turn = 0
 
     @sente.status = "agree_waiting"
-    @gote.status = "agree_waiting"
+    @gote.status  = "agree_waiting"
+
     @id = sprintf("%s+%s+%s+%s+%s", 
     @id = sprintf("%s+%s+%s+%s+%s", 
-                  LEAGUE.event, @game_name, @sente.name, @gote.name,
-                  Time::new.strftime("%Y%m%d%H%M%S"))
+                  LEAGUE.event, @game_name, 
+                  @sente.name, @gote.name, issue_current_time)
+    @logfile = File.join(LEAGUE.dir, @id + ".csa")
 
     LEAGUE.games[@id] = self
 
 
     LEAGUE.games[@id] = self
 
-
     log_message(sprintf("game created %s", @id))
 
     log_message(sprintf("game created %s", @id))
 
-    @logfile = @id + ".csa"
     @board = Board::new
     @board.initial
     @start_time = nil
     @fh = nil
     @board = Board::new
     @board.initial
     @start_time = nil
     @fh = nil
+    @result = nil
 
     propose
   end
   attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
   attr_accessor :last_move, :current_turn
 
     propose
   end
   attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
   attr_accessor :last_move, :current_turn
+  attr_reader   :result
+
+  def rated?
+    @sente.rated? && @gote.rated?
+  end
+
+  def turn?(player)
+    return player.status == "game" && @current_player == player
+  end
 
   def monitoron(monitor)
     @monitors.delete(monitor)
 
   def monitoron(monitor)
     @monitors.delete(monitor)
@@ -1209,7 +1662,7 @@ class Game
   end
 
   def kill(killer)
   end
 
   def kill(killer)
-    if ((@sente.status == "agree_waiting") || (@sente.status == "start_waiting"))
+    if ["agree_waiting", "start_waiting"].include?(@sente.status)
       reject(killer.name)
     elsif (@current_player == killer)
       abnormal_lose()
       reject(killer.name)
     elsif (@current_player == killer)
       abnormal_lose()
@@ -1227,10 +1680,10 @@ class Game
     @sente.status = "connected"
     @gote.status = "connected"
 
     @sente.status = "connected"
     @gote.status = "connected"
 
-    if (@current_player.protocol == "CSA")
+    if (@current_player.protocol == LoginCSA::PROTOCOL)
       @current_player.finish
     end
       @current_player.finish
     end
-    if (@next_player.protocol == "CSA")
+    if (@next_player.protocol == LoginCSA::PROTOCOL)
       @next_player.finish
     end
     @monitors = Array::new
       @next_player.finish
     end
     @monitors = Array::new
@@ -1241,78 +1694,86 @@ class Game
     LEAGUE.games.delete(@id)
   end
 
     LEAGUE.games.delete(@id)
   end
 
+  # class Game
   def handle_one_move(str, player)
   def handle_one_move(str, player)
+    unless turn?(player)
+      return false if str == :timeout
+
+      @fh.puts("'Deferred %s" % [str])
+      log_warning("Deferred a move [%s] scince it is not %s 's turn." %
+                  [str, player.name])
+      player.socket_buffer << str # always in the player's thread
+      return nil
+    end
+
     finish_flag = true
     finish_flag = true
-    if (@current_player == player)
-      @end_time = Time::new
-      t = (@end_time - @start_time).ceil
-      t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
-      
-      move_status = nil
-      if ((@current_player.mytime - t <= 0) && (@total_time > 0))
-        status = :timeout
-      elsif (str == :timeout)
-        return false            # time isn't expired. players aren't swapped. continue game
-      else
-#        begin
-          move_status = @board.handle_one_move(str)
-#        rescue
-#          log_error("handle_one_move raise exception for #{str}")
-#          move_status = :illegal
-#        end
-        if ((move_status == :illegal) || (move_status == :uchifuzme) || (move_status == :oute_kaihimore))
-          @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
-        else
-          if ((move_status == :normal) || (move_status == :outori) || (move_status == :sennichite) || (move_status == :oute_sennichite))
-            @sente.write_safe(sprintf("%s,T%d\n", str, t))
-            @gote.write_safe(sprintf("%s,T%d\n", str, t))
-            @fh.printf("%s\nT%d\n", str, t)
-            @last_move = sprintf("%s,T%d", str, t)
-            @current_turn = @current_turn + 1
-          end
-          @monitors.each do |monitor|
-            monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
-            monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
-          end
-        end
+    @end_time = Time::new
+    t = [(@end_time - @start_time).floor, Least_Time_Per_Move].max
+    
+    move_status = nil
+    if ((@current_player.mytime - t <= -@byoyomi) && 
+        ((@total_time > 0) || (@byoyomi > 0)))
+      status = :timeout
+    elsif (str == :timeout)
+      return false            # time isn't expired. players aren't swapped. continue game
+    else
+      @current_player.mytime -= t
+      if (@current_player.mytime < 0)
+        @current_player.mytime = 0
       end
 
       end
 
-      if (@current_player.mytime - t < @byoyomi)
-        @current_player.mytime = @byoyomi
-      else
-        @current_player.mytime = @current_player.mytime - t
-      end
+      move_status = @board.handle_one_move(str, @sente == @current_player)
 
 
-      if (@next_player.status != "game") # rival is logout or disconnected
-        abnormal_win()
-      elsif (status == :timeout)
-        timeout_lose()
-      elsif (move_status == :illegal)
-        illegal_lose()
-      elsif (move_status == :kachi_win)
-        kachi_win()
-      elsif (move_status == :kachi_lose)
-        kachi_lose()
-      elsif (move_status == :toryo)
-        toryo_lose()
-      elsif (move_status == :outori)
-        outori_win()
-      elsif (move_status == :sennichite)
-        sennichite_draw()
-      elsif (move_status == :oute_sennichite)
-        oute_sennichite_lose()
-      elsif (move_status == :uchifuzume)
-        uchifuzume_lose()
-      elsif (move_status == :oute_kaihimore)
-        oute_kaihimore_lose()
+      if [:illegal, :uchifuzme, :oute_kaihimore].include?(move_status)
+        @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
       else
       else
-        finish_flag = false
+        if [:normal, :outori, :sennichite, :oute_sennichite_sente_lose, :oute_sennichite_gote_lose].include?(move_status)
+          # Thinking time includes network traffic
+          @sente.write_safe(sprintf("%s,T%d\n", str, t))
+          @gote.write_safe(sprintf("%s,T%d\n", str, t))
+          @fh.printf("%s\nT%d\n", str, t)
+          @last_move = sprintf("%s,T%d", str, t)
+          @current_turn += 1
+        end
+
+        @monitors.each do |monitor|
+          monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
+          monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
+        end
       end
       end
-      finish() if finish_flag
-      (@current_player, @next_player) = [@next_player, @current_player]
-      @start_time = Time::new
-      return finish_flag
     end
     end
+
+    if (@next_player.status != "game") # rival is logout or disconnected
+      abnormal_win()
+    elsif (status == :timeout)
+      timeout_lose()
+    elsif (move_status == :illegal)
+      illegal_lose()
+    elsif (move_status == :kachi_win)
+      kachi_win()
+    elsif (move_status == :kachi_lose)
+      kachi_lose()
+    elsif (move_status == :toryo)
+      toryo_lose()
+    elsif (move_status == :outori)
+      outori_win()
+    elsif (move_status == :oute_sennichite_sente_lose)
+      oute_sennichite_win_lose(@gote, @sente) # Sente is checking
+    elsif (move_status == :oute_sennichite_gote_lose)
+      oute_sennichite_win_lose(@sente, @gote) # Gote is checking
+    elsif (move_status == :sennichite)
+      sennichite_draw()
+    elsif (move_status == :uchifuzume)
+      uchifuzume_lose()
+    elsif (move_status == :oute_kaihimore)
+      oute_kaihimore_lose()
+    else
+      finish_flag = false
+    end
+    finish() if finish_flag
+    @current_player, @next_player = @next_player, @current_player
+    @start_time = Time::new
+    return finish_flag
   end
 
   def abnormal_win
   end
 
   def abnormal_win
@@ -1323,6 +1784,8 @@ class Game
     @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)
     @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
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
     end
@@ -1336,6 +1799,8 @@ class Game
     @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)
     @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
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
     end
@@ -1348,18 +1813,26 @@ class Game
     @next_player.write_safe("#SENNICHITE\n#DRAW\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
     @next_player.write_safe("#SENNICHITE\n#DRAW\n")
     @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
   end
 
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
     end
   end
 
-  def oute_sennichite_lose
+  def oute_sennichite_win_lose(winner, loser)
     @current_player.status = "connected"
     @next_player.status = "connected"
     @current_player.status = "connected"
     @next_player.status = "connected"
-    @current_player.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
-    @next_player.write_safe("#OUTE_SENNICHITE\n#WIN\n")
+    loser.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
+    winner.write_safe("#OUTE_SENNICHITE\n#WIN\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.print(@board.to_s.gsub(/^/, "\'"))
-    @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
+    if loser == @current_player
+      @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
+    else
+      @fh.printf("'summary:oute_sennichite:%s win:%s lose\n", @current_player.name, @next_player.name)
+    end
+    @result = GameResultWin.new(winner, loser)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
     end
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
     end
@@ -1372,6 +1845,8 @@ class Game
     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
     @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
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
     end
@@ -1384,6 +1859,8 @@ class Game
     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name)
     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
     @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
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
     end
@@ -1396,6 +1873,8 @@ class Game
     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
     @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
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
     end
@@ -1408,6 +1887,8 @@ class Game
     @next_player.write_safe("#TIME_UP\n#WIN\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
     @next_player.write_safe("#TIME_UP\n#WIN\n")
     @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
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
     end
@@ -1421,6 +1902,8 @@ class Game
     @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)
     @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
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
     end
@@ -1434,6 +1917,8 @@ class Game
     @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)
     @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
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
     end
@@ -1447,6 +1932,8 @@ class Game
     @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)
     @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
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
     end
@@ -1459,6 +1946,8 @@ class Game
     @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
     @fh.print(@board.to_s.gsub(/^/, "\'"))
     @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
     @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
     @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
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
     end
@@ -1474,20 +1963,20 @@ class Game
   end
 
   def propose
   end
 
   def propose
-    begin
-      @fh = open(@logfile, "w")
-      @fh.sync = true
+    @fh = open(@logfile, "w")
+    @fh.sync = true
 
 
-      @fh.printf("V2\n")
-      @fh.printf("N+%s\n", @sente.name)
-      @fh.printf("N-%s\n", @gote.name)
-      @fh.printf("$EVENT:%s\n", @id)
+    @fh.puts("V2")
+    @fh.puts("N+#{@sente.name}")
+    @fh.puts("N-#{@gote.name}")
+    @fh.puts("$EVENT:#{@id}")
 
 
-      @sente.write_safe(propose_message("+"))
-      @gote.write_safe(propose_message("-"))
+    @sente.write_safe(propose_message("+"))
+    @gote.write_safe(propose_message("-"))
 
 
-      @fh.printf("$START_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))
-      @fh.print <<EOM
+    now = Time::new.strftime("%Y/%m/%d %H:%M:%S")
+    @fh.puts("$START_TIME:#{now}")
+    @fh.print <<EOM
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
 P2 * -HI *  *  *  *  * -KA * 
 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
 P2 * -HI *  *  *  *  * -KA * 
 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
@@ -1499,15 +1988,15 @@ P8 * +KA *  *  *  *  * +HI *
 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
 +
 EOM
 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
 +
 EOM
-    end
   end
 
   def show()
     str0 = <<EOM
 BEGIN Game_Summary
   end
 
   def show()
     str0 = <<EOM
 BEGIN Game_Summary
-Protocol_Version:1.0
+Protocol_Version:1.1
 Protocol_Mode:Server
 Format:Shogi 1.0
 Protocol_Mode:Server
 Format:Shogi 1.0
+Declaration:Jishogi 1.1
 Game_ID:#{@id}
 Name+:#{@sente.name}
 Name-:#{@gote.name}
 Game_ID:#{@id}
 Name+:#{@sente.name}
 Name-:#{@gote.name}
@@ -1524,7 +2013,6 @@ Last_Move:#{@last_move}
 Current_Turn:#{@current_turn}
 END Time
 BEGIN Position
 Current_Turn:#{@current_turn}
 END Time
 BEGIN Position
-Jishogi_Declaration:1.1
 EOM
 
     str1 = <<EOM
 EOM
 
     str1 = <<EOM
@@ -1538,9 +2026,10 @@ EOM
   def propose_message(sg_flag)
     str = <<EOM
 BEGIN Game_Summary
   def propose_message(sg_flag)
     str = <<EOM
 BEGIN Game_Summary
-Protocol_Version:1.0
+Protocol_Version:1.1
 Protocol_Mode:Server
 Format:Shogi 1.0
 Protocol_Mode:Server
 Format:Shogi 1.0
+Declaration:Jishogi 1.1
 Game_ID:#{@id}
 Name+:#{@sente.name}
 Name-:#{@gote.name}
 Game_ID:#{@id}
 Name+:#{@sente.name}
 Name-:#{@gote.name}
@@ -1554,7 +2043,6 @@ Byoyomi:#{@byoyomi}
 Least_Time_Per_Move:#{Least_Time_Per_Move}
 END Time
 BEGIN Position
 Least_Time_Per_Move:#{Least_Time_Per_Move}
 END Time
 BEGIN Position
-Jishogi_Declaration:1.1
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
 P2 * -HI *  *  *  *  * -KA * 
 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
 P2 * -HI *  *  *  *  * -KA * 
 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
@@ -1572,7 +2060,24 @@ END Game_Summary
 EOM
     return str
   end
 EOM
     return str
   end
+  
+  private
+  
+  def issue_current_time
+    time = Time::new.strftime("%Y%m%d%H%M%S").to_i
+    @@mutex.synchronize do
+      while time <= @@time do
+        time += 1
+      end
+      @@time = time
+    end
+  end
 end
 end
+end # module ShogiServer
+
+#################################################
+# MAIN
+#
 
 def usage
     print <<EOM
 
 def usage
     print <<EOM
@@ -1580,7 +2085,7 @@ NAME
        shogi-server - server for CSA server protocol
 
 SYNOPSIS
        shogi-server - server for CSA server protocol
 
 SYNOPSIS
-       shogi-server event_name port_number
+       shogi-server [OPTIONS] event_name port_number
 
 DESCRIPTION
        server for CSA server protocol
 
 DESCRIPTION
        server for CSA server protocol
@@ -1588,6 +2093,8 @@ DESCRIPTION
 OPTIONS
        --pid-file file
                specify filename for logging process ID
 OPTIONS
        --pid-file file
                specify filename for logging process ID
+        --daemon dir
+                run as a daemon. Log files will be put in dir.
 
 LICENSE
        this file is distributed under GPL version2 and might be compiled by Exerb
 
 LICENSE
        this file is distributed under GPL version2 and might be compiled by Exerb
@@ -1595,33 +2102,35 @@ LICENSE
 SEE ALSO
 
 RELEASE
 SEE ALSO
 
 RELEASE
-       #{Release}
+       #{ShogiServer::Release}
 
 REVISION
 
 REVISION
-       #{Revision}
+       #{ShogiServer::Revision}
 EOM
 end
 
 EOM
 end
 
+def log_debug(str)
+  $logger.debug(str)
+end
+
 def log_message(str)
 def log_message(str)
-  printf("%s message: %s\n", Time::new.to_s, str)
+  $logger.info(str)
 end
 
 def log_warning(str)
 end
 
 def log_warning(str)
-  printf("%s warning: %s\n", Time::new.to_s, str)
+  $logger.warn(str)
 end
 
 def log_error(str)
 end
 
 def log_error(str)
-  printf("%s error: %s\n", Time::new.to_s, str)
+  $logger.error(str)
 end
 
 
 def parse_command_line
   options = Hash::new
 end
 
 
 def parse_command_line
   options = Hash::new
-  parser = GetoptLong.new
-  parser.ordering = GetoptLong::REQUIRE_ORDER
-  parser.set_options(
-                     ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
-
+  parser = GetoptLong.new(
+    ["--daemon",   GetoptLong::REQUIRED_ARGUMENT],
+    ["--pid-file", GetoptLong::REQUIRED_ARGUMENT])
   parser.quiet = true
   begin
     parser.each_option do |name, arg|
   parser.quiet = true
   begin
     parser.each_option do |name, arg|
@@ -1635,38 +2144,9 @@ def parse_command_line
   return options
 end
 
   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.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|
   open(file, "w") do |fh|
-    fh.print Process::pid, "\n"
+    fh.puts "#{$$}"
   end
 end
 
   end
 end
 
@@ -1674,8 +2154,11 @@ def mutex_watchdog(mutex, sec)
   while true
     begin
       timeout(sec) do
   while true
     begin
       timeout(sec) do
-        mutex.lock
-        mutex.unlock
+        begin
+          mutex.lock
+        ensure
+          mutex.unlock
+        end
       end
       sleep(sec)
     rescue TimeoutError
       end
       sleep(sec)
     rescue TimeoutError
@@ -1685,9 +2168,47 @@ def mutex_watchdog(mutex, sec)
   end
 end
 
   end
 end
 
+def login_loop(client)
+  player = login = nil
+  while r = select([client], nil, nil, ShogiServer::Login_Time) do
+    break unless str = r[0].first.gets
+    $mutex.lock # guards LEAGUE
+    begin
+      str =~ /([\r\n]*)$/
+      eol = $1
+      if (ShogiServer::Login::good_login?(str))
+        player = ShogiServer::Player::new(str, client, eol)
+        login  = ShogiServer::Login::factory(str, player)
+        if (current_player = LEAGUE.find(player.name))
+          if (current_player.password == player.password &&
+              current_player.status != "game")
+            log_message(sprintf("user %s login forcely", player.name))
+            current_player.kill
+          else
+            login.incorrect_duplicated_player(str)
+            player = nil
+            break
+          end
+        end
+        LEAGUE.add(player)
+        break
+      else
+        client.write("LOGIN:incorrect" + eol)
+        client.write("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
+      end
+    ensure
+      $mutex.unlock
+    end
+  end                       # login loop
+  return [player, login]
+end
+
 def main
 def main
+
   $mutex = Mutex::new
   Thread::start do
   $mutex = Mutex::new
   Thread::start do
+    Thread.pass
     mutex_watchdog($mutex, 10)
   end
 
     mutex_watchdog($mutex, 10)
   end
 
@@ -1700,69 +2221,72 @@ def main
   LEAGUE.event = ARGV.shift
   port = ARGV.shift
 
   LEAGUE.event = ARGV.shift
   port = ARGV.shift
 
-  write_pid_file($options["pid-file"]) if ($options["pid-file"])
+  dir = $options["daemon"]
+  dir = File.expand_path(dir) if dir
+  if dir && ! File.exist?(dir)
+    FileUtils.mkdir(dir)
+  end
+  log_file = dir ? File.join(dir, "shogi-server.log") : STDOUT
+  $logger = WEBrick::Log.new(log_file) # thread safe
 
 
+  LEAGUE.dir = dir || File.dirname(__FILE__)
+  LEAGUE.setup_players_database
 
 
-  Thread.abort_on_exception = true
+  config = {}
+  config[:Port]       = port
+  config[:ServerType] = WEBrick::Daemon if $options["daemon"]
+  config[:Logger]     = $logger
+  if $options["pid-file"]
+    pid_file = File.expand_path($options["pid-file"])
+    config[:StartCallback] = Proc.new do
+      write_pid_file(pid_file)
+    end
+    config[:StopCallback] = Proc.new do
+      FileUtils.rm(pid_file, :force => true)
+    end
+  end
 
 
-  server = TCPserver.open(port)
-  log_message("server started")
+  server = WEBrick::GenericServer.new(config)
+  ["INT", "TERM"].each do |signal| 
+    trap(signal) do
+      LEAGUE.shutdown
+      server.shutdown
+    end
+  end
+  $stderr.puts("server started as a deamon [Revision: #{ShogiServer::Revision}]") if $options["daemon"] 
+  log_message("server started [Revision: #{ShogiServer::Revision}]")
+
+  server.start do |client|
+      # client.sync = true # this is already set in WEBrick 
+      client.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
+        # Keepalive time can be set by /proc/sys/net/ipv4/tcp_keepalive_time
+      player, login = login_loop(client) # loop
+      next unless player
 
 
-  while true
-    Thread::start(server.accept) do |client|
-      client.sync = true
-      player = nil
-      while (str = client.gets_timeout(Login_Time))
-        begin
-          $mutex.lock
-          str =~ /([\r\n]*)$/
-          eol = $1
-          if (good_login?(str))
-            player = Player::new(str, client)
-            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
-                Thread::exit
-              end
-            end
-            LEAGUE.add(player)
-            break
-          else
-            client.write_safe("LOGIN:incorrect" + eol)
-            client.write_safe("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
-          end
-        ensure
-          $mutex.unlock
-        end
-      end                       # login loop
-      if (! player)
-        client.close
-        Thread::exit
-      end
       log_message(sprintf("user %s login", player.name))
       log_message(sprintf("user %s login", player.name))
-      player.run
+      login.process
+      player.run(login.csa_1st_str) # loop
       begin
         $mutex.lock
         if (player.game)
           player.game.kill(player)
         end
       begin
         $mutex.lock
         if (player.game)
           player.game.kill(player)
         end
-        player.finish
+        player.finish # socket has been closed
         LEAGUE.delete(player)
         log_message(sprintf("user %s logout", player.name))
       ensure
         $mutex.unlock
       end
         LEAGUE.delete(player)
         log_message(sprintf("user %s logout", player.name))
       ensure
         $mutex.unlock
       end
-    end
   end
 end
 
   end
 end
 
+
 if ($0 == __FILE__)
 if ($0 == __FILE__)
-  LEAGUE = League::new
+  STDOUT.sync = true
+  STDERR.sync = true
+  TCPSocket.do_not_reverse_lookup = true
+  Thread.abort_on_exception = $DEBUG ? true : false
+
+  LEAGUE = ShogiServer::League::new
   main
 end
   main
 end