OSDN Git Service

* [news]
[shogi-server/shogi-server.git] / shogi-server
index 21f7829..6a77f86 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,453 @@ 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
+
+    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} #{ex.backtrace}")
+          end
+        end
+      end
+    end
 
 
-TCPSocket.do_not_reverse_lookup = true
+    def shutdown
+      @thread.kill if @thread
+    end
 
 
+    # private
 
 
-class TCPSocket
-  def gets_timeout(t = Default_Timeout)
-    begin
-      timeout(t) do
-        return self.gets
+    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
+      # 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
+
+  #
+  # This manages those players who have their player_id.
+  # Since mk_rate mainly updates the yaml file, basically,
+  # this only reads data. But this writes some properties.
+  # TODO Such data should be facoted out to another file
+  #
+  class Persistent
+    def initialize(filename)
+      @db = YAML::Store.new(filename)
+      @db.transaction do |pstore|
+        @db['players'] ||= Hash.new
       end
       end
-    rescue TimeoutError
-      return nil
-    rescue
-      return nil
     end
     end
-  end
-  def gets_safe(t = nil)
-    if (t && t > 0)
-      begin
-        timeout(t) do
-          return self.gets
+
+    #
+    # trancaction=true means read only
+    #
+    def each_group(transaction=false)
+      @db.transaction(transaction) do
+        groups = @db["players"] || Hash.new
+        groups.each do |group, players|
+          yield group,players
         end
         end
-      rescue TimeoutError
-        return :timeout
-      rescue
-        return nil
-      end
-    else
-      begin
-        return self.gets
-      rescue
-        return nil
       end
     end
       end
     end
-  end
-  def write_safe(str)
-    begin
-      return self.write(str)
-    rescue
-      return nil
+
+    def load_player(player)
+      return unless player.player_id
+
+      hash = nil
+      each_group(true) do |group, players|
+        hash = players[player.player_id]
+        break if hash
+      end
+      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)
+      return unless player.player_id
+
+      each_group do |group, players|
+        hash = players[player.player_id]
+        if hash
+          # write only this property. 
+          # the others are updated by ./mk_rate
+          hash['last_game_win'] = player.last_game_win
+          break
+        end
+      end
     end
     end
-  end
-end
 
 
+    def get_players
+      players = []
+      each_group(true) do |group, players_hash|
+        players << players_hash.keys
+      end
+      return players.flatten.collect do |player_id|
+        p = BasicPlayer.new
+        p.player_id = player_id
+        load_player(p)
+        p
+      end
+    end
+  end # class Persistent
 
 
-class League
   def initialize
   def initialize
+    @mutex = Mutex.new # guard @players
     @games = Hash::new
     @players = Hash::new
     @event = nil
     @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 do |name, player| 
+        @persistent.save(player)
+      end
+    end
+    @floodgate.shutdown
+  end
+
+  # this should be called just after instanciating a League object.
+  def setup_players_database
+    filename = File.join(@dir, "players.yaml")
+    @persistent = Persistent.new(filename)
   end
   end
-  attr_accessor :players, :games, :event
 
   def add(player)
 
   def add(player)
-    @players[player.name] = player
+    @persistent.load_player(player)
+    @mutex.synchronize do
+      @players[player.name] = player
+    end
   end
   end
+  
   def delete(player)
   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
+    @persistent.save(player)
+    @mutex.synchronize do
+      @players.delete(player.name)
+    end
+  end
+
+  def reload
+    @mutex.synchronize do
+      @players.each do |name, player| 
+        @persistent.load_player(player)
+      end
+    end
+  end
+
+  def find_all_players
+    found = nil
+    @mutex.synchronize do
+      found = @players.find_all do |name, player|
+        yield player
       end
     end
       end
     end
-    return nil
+    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 rated_players
+    return @persistent.get_players
+  end
+end
+
+
+######################################################
+# 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
 
   end
 end
 
-class Player
-  def initialize(str, socket)
+######################################################
+# 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
+    @player_id = nil
     @name = nil
     @password = nil
     @name = nil
     @password = nil
+    @rate = 0
+    @win  = 0
+    @loss = 0
+    @last_game_win = false
+  end
+
+  # Idetifier of the player in the rating system
+  attr_accessor :player_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?
+    @player_id != nil
+  end
+
+  def last_game_win?
+    return @last_game_win
+  end
+
+  def simple_player_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 @player_id and @trip
+  #
+  def set_password(str)
+    if str && !str.empty?
+      @password = str.strip
+      @player_id   = "%s+%s" % [@name, Digest::MD5.hexdigest(@password)]
+    else
+      @player_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 +515,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 +524,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 +647,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_player_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 +679,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 +725,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 +746,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 +768,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
@@ -510,6 +909,10 @@ class Piece
     @y = y
   end
 
     @y = y
   end
 
+  def point
+    @point
+  end
+
   def name
     @name
   end
   def name
     @name
   end
@@ -535,6 +938,7 @@ end
 
 class PieceFU < Piece
   def initialize(*arg)
 
 class PieceFU < Piece
   def initialize(*arg)
+    @point = 1
     @normal_moves = [[0, +1]]
     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
     @name = "FU"
     @normal_moves = [[0, +1]]
     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
     @name = "FU"
@@ -568,6 +972,7 @@ end
 
 class PieceKY  < Piece
   def initialize(*arg)
 
 class PieceKY  < Piece
   def initialize(*arg)
+    @point = 1
     @normal_moves = []
     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
     @name = "KY"
     @normal_moves = []
     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
     @name = "KY"
@@ -612,6 +1017,7 @@ class PieceKY  < Piece
 end
 class PieceKE  < Piece
   def initialize(*arg)
 end
 class PieceKE  < Piece
   def initialize(*arg)
+    @point = 1
     @normal_moves = [[+1, +2], [-1, +2]]
     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
     @name = "KE"
     @normal_moves = [[+1, +2], [-1, +2]]
     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
     @name = "KE"
@@ -631,6 +1037,7 @@ class PieceKE  < Piece
 end
 class PieceGI  < Piece
   def initialize(*arg)
 end
 class PieceGI  < Piece
   def initialize(*arg)
+    @point = 1
     @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, -1], [-1, -1]]
     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
     @name = "GI"
     @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, -1], [-1, -1]]
     @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
     @name = "GI"
@@ -640,6 +1047,7 @@ class PieceGI  < Piece
 end
 class PieceKI  < Piece
   def initialize(*arg)
 end
 class PieceKI  < Piece
   def initialize(*arg)
+    @point = 1
     @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
     @promoted_moves = []
     @name = "KI"
     @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
     @promoted_moves = []
     @name = "KI"
@@ -649,6 +1057,7 @@ class PieceKI  < Piece
 end
 class PieceKA  < Piece
   def initialize(*arg)
 end
 class PieceKA  < Piece
   def initialize(*arg)
+    @point = 5
     @normal_moves = []
     @promoted_moves = [[0, +1], [+1, 0], [-1, 0], [0, -1]]
     @name = "KA"
     @normal_moves = []
     @promoted_moves = [[0, +1], [+1, 0], [-1, 0], [0, -1]]
     @name = "KA"
@@ -698,6 +1107,7 @@ class PieceKA  < Piece
 end
 class PieceHI  < Piece
   def initialize(*arg)
 end
 class PieceHI  < Piece
   def initialize(*arg)
+    @point = 5
     @normal_moves = []
     @promoted_moves = [[+1, +1], [-1, +1], [+1, -1], [-1, -1]]
     @name = "HI"
     @normal_moves = []
     @promoted_moves = [[+1, +1], [-1, +1], [+1, -1], [-1, -1]]
     @name = "HI"
@@ -743,6 +1153,7 @@ class PieceHI  < Piece
 end
 class PieceOU < Piece
   def initialize(*arg)
 end
 class PieceOU < Piece
   def initialize(*arg)
+    @point = 0
     @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1], [+1, -1], [-1, -1]]
     @promoted_moves = []
     @name = "OU"
     @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1], [+1, -1], [-1, -1]]
     @promoted_moves = []
     @name = "OU"
@@ -754,13 +1165,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)
@@ -774,15 +1188,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)
@@ -795,15 +1203,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)
@@ -822,25 +1225,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
 
@@ -861,6 +1266,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
@@ -893,7 +1299,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
@@ -903,9 +1309,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)
@@ -923,17 +1328,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
@@ -942,31 +1360,113 @@ 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
 
       return true
     end
     return false
   end
 
-  def handle_one_move(str)
+  def good_kachi?(sente)
+    if (checkmated?(sente))
+      puts "'NG: Checkmating." if $DEBUG
+      return false 
+    end
+    
+    ou = look_for_ou(sente)
+    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
+
+    if (sente)
+      hands = @sente_hands
+      r = [1, 2, 3]
+    else
+      hands = @gote_hands
+      r = [7, 8, 9]
+    end
+    r.each do |y|
+      x = 1
+      while (x <= 9)
+        if (@array[x][y] &&
+            (@array[x][y].sente == sente) &&
+            (@array[x][y].point > 0))
+          point = point + @array[x][y].point
+          number = number + 1
+        end
+        x = x + 1
+      end
+    end
+    hands.each do |piece|
+      point = point + piece.point
+    end
+
+    if (number < 10)
+      puts "'NG: Piece#[%d] is too small." % [number] if $DEBUG
+      return false     
+    end  
+    if (sente)
+      if (point < 28)
+        puts "'NG: Black's point#[%d] is too small." % [point] if $DEBUG
+        return false 
+      end  
+    else
+      if (point < 27)
+        puts "'NG: White's point#[%d] is too small." % [point] if $DEBUG
+        return false 
+      end
+    end
+
+    puts "'Good: Piece#[%d], Point[%d]." % [number, point] if $DEBUG
+    return true
+  end
+
+  # 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
@@ -975,7 +1475,12 @@ class Board
       y1 = $5.to_i
       name = $6
     elsif (str =~ /^%KACHI/)
       y1 = $5.to_i
       name = $6
     elsif (str =~ /^%KACHI/)
-      return :kachi
+      raise ArgumentError, "sente is null", caller if sente == nil
+      if (good_kachi?(sente))
+        return :kachi_win
+      else
+        return :kachi_lose
+      end
     elsif (str =~ /^%TORYO/)
       return :toryo
     else
     elsif (str =~ /^%TORYO/)
       return :toryo
     else
@@ -990,10 +1495,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
     
@@ -1019,7 +1526,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))
@@ -1027,22 +1536,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
 
@@ -1079,12 +1574,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.player_id || @black.name
+    white_name = @white.player_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
@@ -1094,42 +1632,57 @@ 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"
-    @id = sprintf("%s+%s+%s+%s+%s", 
-                  LEAGUE.event, @game_name, @sente.name, @gote.name,
-                  Time::new.strftime("%Y%m%d%H%M%S"))
+    @gote.status  = "agree_waiting"
 
 
-    LEAGUE.games[@id] = self
+    @game_id = sprintf("%s+%s+%s+%s+%s", 
+                  LEAGUE.event, @game_name, 
+                  @sente.name, @gote.name, issue_current_time)
+    
+    now = Time.now
+    log_dir_name = File.join(LEAGUE.dir, 
+                             now.strftime("%Y"),
+                             now.strftime("%m"),
+                             now.strftime("%d"))
+    FileUtils.mkdir_p(log_dir_name) unless File.exist?(log_dir_name)
+    @logfile = File.join(log_dir_name, @game_id + ".csa")
 
 
+    LEAGUE.games[@game_id] = self
 
 
-    log_message(sprintf("game created %s", @id))
+    log_message(sprintf("game created %s", @game_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
 
     propose
   end
-  attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
+  attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :game_id, :board, :current_player, :next_player, :fh, :monitors
   attr_accessor :last_move, :current_turn
   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)
@@ -1141,13 +1694,13 @@ class Game
   end
 
   def reject(rejector)
   end
 
   def reject(rejector)
-    @sente.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
-    @gote.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
+    @sente.write_safe(sprintf("REJECT:%s by %s\n", @game_id, rejector))
+    @gote.write_safe(sprintf("REJECT:%s by %s\n", @game_id, rejector))
     finish
   end
 
   def kill(killer)
     finish
   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()
@@ -1156,7 +1709,7 @@ class Game
   end
 
   def finish
   end
 
   def finish
-    log_message(sprintf("game finished %s", @id))
+    log_message(sprintf("game finished %s", @game_id))
     @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))    
     @fh.close
 
     @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))    
     @fh.close
 
@@ -1165,10 +1718,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
@@ -1176,79 +1729,89 @@ class Game
     @gote = nil
     @current_player = nil
     @next_player = nil
     @gote = nil
     @current_player = nil
     @next_player = nil
-    LEAGUE.games.delete(@id)
+    LEAGUE.games.delete(@game_id)
   end
 
   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)
-        kachi_win()
-      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, :uchifuzume, :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][#{@game_id}] "))
+          monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @game_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
@@ -1259,8 +1822,10 @@ 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|
     @monitors.each do |monitor|
-      monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
+      monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @game_id))
     end
   end
 
     end
   end
 
@@ -1272,8 +1837,10 @@ 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|
     @monitors.each do |monitor|
-      monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
+      monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @game_id))
     end
   end
 
     end
   end
 
@@ -1284,20 +1851,28 @@ 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|
     @monitors.each do |monitor|
-      monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
+      monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @game_id))
     end
   end
 
     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|
     @monitors.each do |monitor|
-      monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
+      monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @game_id))
     end
   end
 
     end
   end
 
@@ -1308,8 +1883,10 @@ 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|
     @monitors.each do |monitor|
-      monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
+      monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @game_id))
     end
   end
 
     end
   end
 
@@ -1320,8 +1897,10 @@ 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|
     @monitors.each do |monitor|
-      monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
+      monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @game_id))
     end
   end
 
     end
   end
 
@@ -1332,8 +1911,10 @@ 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|
     @monitors.each do |monitor|
-      monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
+      monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @game_id))
     end
   end
 
     end
   end
 
@@ -1344,8 +1925,10 @@ 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|
     @monitors.each do |monitor|
-      monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
+      monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @game_id))
     end
   end
 
     end
   end
 
@@ -1357,8 +1940,25 @@ 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|
     @monitors.each do |monitor|
-      monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
+      monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @game_id))
+    end
+  end
+
+  def kachi_lose
+    @current_player.status = "connected"
+    @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")
+    @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", @game_id))
     end
   end
 
     end
   end
 
@@ -1370,8 +1970,10 @@ 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|
     @monitors.each do |monitor|
-      monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
+      monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @game_id))
     end
   end
 
     end
   end
 
@@ -1382,35 +1984,37 @@ 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|
     @monitors.each do |monitor|
-      monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
+      monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @game_id))
     end
   end
 
   def start
     end
   end
 
   def start
-    log_message(sprintf("game started %s", @id))
-    @sente.write_safe(sprintf("START:%s\n", @id))
-    @gote.write_safe(sprintf("START:%s\n", @id))
+    log_message(sprintf("game started %s", @game_id))
+    @sente.write_safe(sprintf("START:%s\n", @game_id))
+    @gote.write_safe(sprintf("START:%s\n", @game_id))
     @sente.mytime = @total_time
     @gote.mytime = @total_time
     @start_time = Time::new
   end
 
   def propose
     @sente.mytime = @total_time
     @gote.mytime = @total_time
     @start_time = Time::new
   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:#{@game_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
@@ -1422,16 +2026,16 @@ 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
-Game_ID:#{@id}
+Declaration:Jishogi 1.1
+Game_ID:#{@game_id}
 Name+:#{@sente.name}
 Name-:#{@gote.name}
 Rematch_On_Draw:NO
 Name+:#{@sente.name}
 Name-:#{@gote.name}
 Rematch_On_Draw:NO
@@ -1447,7 +2051,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
@@ -1461,10 +2064,11 @@ 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
-Game_ID:#{@id}
+Declaration:Jishogi 1.1
+Game_ID:#{@game_id}
 Name+:#{@sente.name}
 Name-:#{@gote.name}
 Your_Turn:#{sg_flag}
 Name+:#{@sente.name}
 Name-:#{@gote.name}
 Your_Turn:#{sg_flag}
@@ -1477,7 +2081,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
@@ -1495,7 +2098,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
@@ -1503,7 +2123,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
@@ -1511,6 +2131,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
@@ -1518,33 +2140,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|
@@ -1558,38 +2182,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
 
@@ -1597,8 +2192,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
@@ -1608,9 +2206,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
 
@@ -1623,69 +2259,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