OSDN Git Service

Improved a way to handle diferred moves (2008-03-24's change),
[shogi-server/shogi-server.git] / shogi-server
index 7c94fae..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,106 +55,183 @@ 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
-
-require 'getoptlong'
-require 'thread'
-require 'timeout'
-require 'socket'
-require 'yaml'
-require 'yaml/store'
-require 'digest/md5'
-
-TCPSocket.do_not_reverse_lookup = true
-Thread.abort_on_exception = true
-
+class League
 
 
-class TCPSocket
-  def gets_timeout(t = Default_Timeout)
-    begin
-      timeout(t) do
-        return self.gets
+  class Floodgate
+    class << self
+      def game_name?(str)
+        return /^floodgate-\d+-\d+$/.match(str) ? true : false
       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
+
+    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
-      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 shutdown
+      @thread.kill if @thread
     end
     end
-  end
-end
 
 
+    # 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
+      # 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
 
 
-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
-    @db = YAML::Store.new( File.join(File.dirname(__FILE__), "players.yaml") )
+    @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
+    @floodgate.shutdown
+  end
+
+  # this should be called just after instanciating a League object.
+  def setup_players_database
+    @db = YAML::Store.new(File.join(@dir, "players.yaml"))
   end
   end
-  attr_accessor :players, :games, :event
 
   def add(player)
     self.load(player) if player.id
 
   def add(player)
     self.load(player) if player.id
-    @players[player.name] = player
+    @mutex.synchronize do
+      @players[player.name] = player
+    end
   end
   
   def delete(player)
   end
   
   def delete(player)
-    @players.delete(player.name)
+    @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
   
   end
   
-  def get_player(status, game_name, sente, searcher=nil)
-    @players.each do |name, player|
-      if ((player.status == status) &&
-          (player.game_name == game_name) &&
-          ((sente == nil) || (player.sente == nil) || (player.sente == sente)) &&
-          ((searcher == nil) || (player != searcher)))
-        return player
+  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
       end
     end
-    return nil
+    return found ? found.last : nil
   end
   
   def load(player)
     hash = search(player.id)
   end
   
   def load(player)
     hash = search(player.id)
-    if hash
-      # a current user
-      player.name = hash['name']
-      player.rate = hash['rate']
-      player.modified_at = hash['last_modified']
+    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
     end
   end
 
   def search(id)
     hash = nil
     end
   end
 
   def search(id)
     hash = nil
-    @db.transaction do
-      hash = @db[id]
+    @db.transaction(true) do
+      break unless  @db["players"]
+      @db["players"].each do |group, players|
+        hash = players[id]
+        break if hash
+      end
     end
     hash
   end
     end
     hash
   end
@@ -138,11 +239,12 @@ class League
   def rated_players
     players = []
     @db.transaction(true) do
   def rated_players
     players = []
     @db.transaction(true) do
-      @db.roots.each do |id|
-        players << id
+      break unless  @db["players"]
+      @db["players"].each do |group, players_hash|
+        players << players_hash.keys
       end
     end
       end
     end
-    return players.collect do |id|
+    return players.flatten.collect do |id|
       p = BasicPlayer.new
       p.id = id
       self.load(p)
       p = BasicPlayer.new
       p.id = id
       self.load(p)
@@ -152,7 +254,135 @@ class League
 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
+
+######################################################
+# 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
 class BasicPlayer
+  def initialize
+    @id = nil
+    @name = nil
+    @password = nil
+    @last_game_win = false
+  end
+
   # Idetifier of the player in the rating system
   attr_accessor :id
 
   # Idetifier of the player in the rating system
   attr_accessor :id
 
@@ -164,16 +394,18 @@ class BasicPlayer
 
   # Score in the rating sysem
   attr_accessor :rate
 
   # 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
 
   # Last timestamp when the rate was modified
   attr_accessor :modified_at
 
-
-
-  def initialize
-    @name = nil
-    @password = nil
-  end
+  # Whether win the previous game or not
+  attr_accessor :last_game_win
 
   def modified_at
     @modified_at || Time.now
 
   def modified_at
     @modified_at || Time.now
@@ -190,6 +422,10 @@ class BasicPlayer
     @id != nil
   end
 
     @id != nil
   end
 
+  def last_game_win?
+    return @last_game_win
+  end
+
   def simple_id
     if @trip
       simple_name = @name.gsub(/@.*?$/, '')
   def simple_id
     if @trip
       simple_name = @name.gsub(/@.*?$/, '')
@@ -212,27 +448,28 @@ class BasicPlayer
   end
 end
 
   end
 end
 
+
 class Player < BasicPlayer
 class Player < BasicPlayer
-  def initialize(str, socket)
+  def initialize(str, socket, eol=nil)
     super()
     @socket = socket
     super()
     @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
 
   attr_accessor :socket, :status
   attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente
   end
 
   attr_accessor :socket, :status
   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))
   
   def kill
     log_message(sprintf("user %s killed", @name))
@@ -247,9 +484,8 @@ class Player < BasicPlayer
     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
@@ -257,20 +493,25 @@ class Player < BasicPlayer
   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)
       if (@sente)
         return sprintf("%s %s %s %s +", @name, @protocol, @status, @game_name)
       elsif (@sente == false)
@@ -283,79 +524,64 @@ class Player < BasicPlayer
     end
   end
 
     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
-    set_password(password)
-    if (ext)
-      @protocol = "x1"
-    else
-      @protocol = "CSA"
-    end
-    @main_thread = Thread::current
-    @writer_thread = Thread::start do
-      writer()
-    end
-  end
-  
-  def run
-    write_safe(sprintf("LOGIN:%s OK\n", @name))
-    if (@protocol != "CSA")
-      log_message(sprintf("user %s run in %s mode", @name, @protocol))
-      write_safe(sprintf("##[LOGIN] +OK %s\n", @protocol))
-    else
-      log_message(sprintf("user %s run in CSA mode", @name))
-      if (good_game_name?(@password))
-        csa_1st_str = "%%GAME #{@password} *"
-      else
-        csa_1st_str = "%%GAME #{Default_Game_Name} *"
-      end
-    end
-    
-    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
+        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
         when /^[\+\-][^%]/
           if (@status == "game")
             array_str = str.split(",")
             move = array_str.shift
             additional = array_str.shift
             if /^'(.*)/ =~ additional
-              comment = array_str.unshift("'*#{$1}")
+              comment = array_str.unshift("'*#{$1.toeuc}")
             end
             s = @game.handle_one_move(move, self)
             end
             s = @game.handle_one_move(move, self)
-            @game.fh.print("#{comment}\n") if (comment && !s)
-            return if (s && @protocol == "CSA")
+            @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)
           end
         when /^%[^%]/, :timeout
           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
@@ -390,7 +616,8 @@ class Player < BasicPlayer
             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|
         when /^%%RATING/
           players = LEAGUE.rated_players
           players.sort {|a,b| b.rate <=> a.rate}.each do |p|
@@ -412,7 +639,7 @@ class Player < BasicPlayer
           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"))
@@ -421,24 +648,28 @@ class Player < BasicPlayer
             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 ((my_sente_str == "*") && (rival.sente == nil))
           if (rival)
             @game_name = game_name
             if ((my_sente_str == "*") && (rival.sente == nil))
@@ -463,8 +694,6 @@ class Player < BasicPlayer
               ## 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"
@@ -486,7 +715,7 @@ class Player < BasicPlayer
         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")
+            if (player.protocol != LoginCSA::PROTOCOL)
               player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message)) 
             end
           end
               player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message)) 
             end
           end
@@ -508,20 +737,27 @@ class Player < BasicPlayer
           @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
@@ -898,12 +1134,13 @@ 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 = [[], [], [], [], [], [], [], [], [], []]
     @move_count = 0
     @array = [[], [], [], [], [], [], [], [], [], []]
     @move_count = 0
+    @teban = nil # black => true, white => false
   end
   attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
   attr_reader :move_count
   end
   attr_accessor :array, :sente_hands, :gote_hands, :history, :sente_history, :gote_history
   attr_reader :move_count
@@ -920,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)
@@ -941,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)
@@ -968,26 +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
     @move_count += 1
           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
 
@@ -1008,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
@@ -1040,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
@@ -1050,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)
@@ -1070,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
@@ -1089,25 +1329,44 @@ 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
       return true
     end
     return false
@@ -1175,6 +1434,7 @@ class Board
     return true
   end
 
     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
   def handle_one_move(str, sente=nil)
     if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
       sg = $1
@@ -1204,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
     
@@ -1233,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))
@@ -1241,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
 
@@ -1293,7 +1543,7 @@ 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
@@ -1302,9 +1552,7 @@ class GameResult
   attr_reader :players, :black, :white
 
   def initialize(p1, p2)
   attr_reader :players, :black, :white
 
   def initialize(p1, p2)
-    @players = []
-    @players << p1
-    @players << p2
+    @players = [p1, p2]
     if p1.sente && !p2.sente
       @black, @white = p1, p2
     elsif !p1.sente && p2.sente
     if p1.sente && !p2.sente
       @black, @white = p1, p2
     elsif !p1.sente && p2.sente
@@ -1321,6 +1569,8 @@ class GameResultWin < GameResult
   def initialize(winner, loser)
     super
     @winner, @loser = 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
   end
 
   def to_s
@@ -1331,7 +1581,11 @@ class GameResultWin < GameResult
 end
 
 class GameResultDraw < GameResult
 end
 
 class GameResultDraw < GameResult
-
+  def initialize(p1, p2)
+    super
+    p1.last_game_win = false
+    p2.last_game_win = false
+  end
 end
 
 class Game
 end
 
 class Game
@@ -1347,27 +1601,26 @@ 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, issue_current_time)
-    @logfile = @id + ".csa"
+                  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
 
@@ -1389,6 +1642,10 @@ class Game
     @sente.rated? && @gote.rated?
   end
 
     @sente.rated? && @gote.rated?
   end
 
+  def turn?(player)
+    return player.status == "game" && @current_player == player
+  end
+
   def monitoron(monitor)
     @monitors.delete(monitor)
     @monitors.push(monitor)
   def monitoron(monitor)
     @monitors.delete(monitor)
     @monitors.push(monitor)
@@ -1405,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()
@@ -1423,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
@@ -1437,79 +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).floor
-      t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
-      
-      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 = @current_player.mytime - t
-        if (@current_player.mytime < 0)
-          @current_player.mytime = 0
-        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
 
 
-#        begin
-          move_status = @board.handle_one_move(str, @sente == @current_player)
-#        rescue
-#          log_error("handle_one_move raise exception for #{str}")
-#          move_status = :illegal
-#        end
+      move_status = @board.handle_one_move(str, @sente == @current_player)
 
 
-        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
+      if [:illegal, :uchifuzme, :oute_kaihimore].include?(move_status)
+        @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
+      else
+        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
+        @monitors.each do |monitor|
+          monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
+          monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
         end
       end
         end
       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 == :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()
-      else
-        finish_flag = false
-      end
-      finish() if finish_flag
-      (@current_player, @next_player) = [@next_player, @current_player]
-      @start_time = Time::new
-      return finish_flag
+    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
     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
@@ -1556,14 +1820,18 @@ class Game
     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)
-    @result = GameResultWin.new(@next_player, @current_player)
+    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))
     @fh.printf("'rating:#{@result.to_s}\n") if rated?
     @monitors.each do |monitor|
       monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
@@ -1695,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
@@ -1720,7 +1988,6 @@ 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()
   end
 
   def show()
@@ -1806,6 +2073,7 @@ EOM
     end
   end
 end
     end
   end
 end
+end # module ShogiServer
 
 #################################################
 # MAIN
 
 #################################################
 # MAIN
@@ -1817,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
@@ -1825,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
@@ -1832,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|
@@ -1872,37 +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_@\-\.]{1,#{Max_Identifier_Length}}\z/
-    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
 
@@ -1924,7 +2168,44 @@ 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
     Thread.pass
   $mutex = Mutex::new
   Thread::start do
     Thread.pass
@@ -1940,53 +2221,51 @@ 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
 
 
-  server = TCPserver.open(port)
-  log_message("server started")
+  LEAGUE.dir = dir || File.dirname(__FILE__)
+  LEAGUE.setup_players_database
+
+  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 = 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|
-      Thread.pass
-      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
-                return
-              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
-        return
-      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)
       begin
         $mutex.lock
         if (player.game)
@@ -1998,11 +2277,16 @@ def main
       ensure
         $mutex.unlock
       end
       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