OSDN Git Service

Improved a way to handle diferred moves (2008-03-24's change),
[shogi-server/shogi-server.git] / shogi-server
index b8c8ac4..5d33d6f 100755 (executable)
@@ -18,6 +18,7 @@
 ## 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
 
+require 'kconv'
 require 'getoptlong'
 require 'thread'
 require 'timeout'
 require 'getoptlong'
 require 'thread'
 require 'timeout'
@@ -28,52 +29,19 @@ require 'digest/md5'
 require 'webrick'
 require 'fileutils'
 
 require 'webrick'
 require 'fileutils'
 
-
-class TCPSocket
-  def gets_timeout(t = Default_Timeout)
-    begin
-      timeout(t) do
-        return self.gets
-      end
-    rescue TimeoutError
-      return nil
-    rescue
-      return nil
-    end
-  end
-  def gets_safe(t = nil)
-    if (t && t > 0)
-      begin
-        timeout(t) do
-          return self.gets
-        end
-      rescue TimeoutError
-        return :timeout
-      rescue Exception => ex
-        log_error("#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}")
-        return :exception
-      end
-    else
-      begin
-        return self.gets
-      rescue
-        return nil
-      end
-    end
-  end
-  def write_safe(str)
-    begin
-      return self.write(str)
-    rescue
-      return nil
-    end
+def gets_safe(socket, timeout=nil)
+  if r = select([socket], nil, nil, timeout)
+    return r[0].first.gets
+  else
+    return :timeout
   end
   end
+rescue Exception => ex
+  log_error("#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}")
+  return :exception
 end
 
 end
 
-
 module ShogiServer # for a namespace
 
 module ShogiServer # for a namespace
 
-Max_Write_Queue_Size = 1000
 Max_Identifier_Length = 32
 Default_Timeout = 60            # for single socket operation
 
 Max_Identifier_Length = 32
 Default_Timeout = 60            # for single socket operation
 
@@ -87,14 +55,12 @@ Release = "$Name$".split[1].sub(/\A[^\d]*/, '').gsub(/_/, '.')
 Release.concat("-") if (Release == "")
 Revision = "$Revision$".gsub(/[^\.\d]/, '')
 
 Release.concat("-") if (Release == "")
 Revision = "$Revision$".gsub(/[^\.\d]/, '')
 
-
-
 class League
 
   class Floodgate
     class << self
       def game_name?(str)
 class League
 
   class Floodgate
     class << self
       def game_name?(str)
-        return "wdoor-900-0" == str
+        return /^floodgate-\d+-\d+$/.match(str) ? true : false
       end
     end
 
       end
     end
 
@@ -109,8 +75,9 @@ class League
         Thread.pass
         while (true)
           begin
         Thread.pass
         while (true)
           begin
-            sleep(20)
+            sleep(10)
             next if Time.now < @next_time
             next if Time.now < @next_time
+            @league.reload
             match_game
             charge
           rescue Exception => ex 
             match_game
             charge
           rescue Exception => ex 
@@ -132,13 +99,13 @@ class League
       if now.min < 30
         @next_time = Time.mktime(now.year, now.month, now.day, now.hour, 30)
       else
       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+1)
+        @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
       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 + 1)
+      #   @next_time = Time.mktime(now.year, now.month, now.day, now.hour, now.min) + 60
       # end
     end
 
       # end
     end
 
@@ -148,36 +115,9 @@ class League
         Floodgate.game_name?(pl.game_name) &&
         pl.sente == nil
       end
         Floodgate.game_name?(pl.game_name) &&
         pl.sente == nil
       end
-      random_match(players)
-    end
-
-    def random_match(players)
-      if players.size < 2
-        log_message("Floodgate: too few players [%d]" % [players.size])
-        return
-      end
-      log_message("Floodgate: found %d players. Making games..." % [players.size])
-      random_players = players.sort{ rand < 0.5 ? 1 : -1 }
-      pairs = [[random_players.shift]]
-      while !random_players.empty? do
-        if pairs.last.size < 2
-          pairs.last << random_players.shift
-        else
-          pairs << [random_players.shift]
-        end 
-      end
-      if pairs.last.size < 2
-        pairs.pop
-      end
-      pairs.each do |pair|
-        start_game(pair.first, pair.last)
-      end
-    end
-
-    def start_game(p1, p2)
-      p1.sente = true
-      p2.sente = false
-      Game.new(p1.game_name, p1, p2)
+      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
 
     end
   end # class Floodgate
 
@@ -193,6 +133,9 @@ class League
   attr_accessor :players, :games, :event, :dir
 
   def shutdown
   attr_accessor :players, :games, :event, :dir
 
   def shutdown
+    @mutex.synchronize do
+      @players.each {|a| save(a)}
+    end
     @floodgate.shutdown
   end
 
     @floodgate.shutdown
   end
 
@@ -210,10 +153,17 @@ class League
   
   def delete(player)
     @mutex.synchronize do
   
   def delete(player)
     @mutex.synchronize do
+      save(player)
       @players.delete(player.name)
     end
   end
 
       @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
   def find_all_players
     found = nil
     @mutex.synchronize do
@@ -224,6 +174,14 @@ class League
     return found.map {|a| a.last}
   end
   
     return found.map {|a| a.last}
   end
   
+  def find(player_name)
+    found = nil
+    @mutex.synchronize do
+      found = @players[player_name]
+    end
+    return found
+  end
+
   def get_player(status, game_name, sente, searcher)
     found = nil
     @mutex.synchronize do
   def get_player(status, game_name, sente, searcher)
     found = nil
     @mutex.synchronize do
@@ -241,18 +199,34 @@ class League
   
   def load(player)
     hash = search(player.id)
   
   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']
-      player.rating_group = hash['rating_group']
+    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
+    @db.transaction(true) do
       break unless  @db["players"]
       @db["players"].each do |group, players|
         hash = players[id]
       break unless  @db["players"]
       @db["players"].each do |group, players|
         hash = players[id]
@@ -286,7 +260,8 @@ end
 class Login
   def Login.good_login?(str)
     tokens = str.split
 class Login
   def Login.good_login?(str)
     tokens = str.split
-    if (((tokens.length == 3) || ((tokens.length == 4) && tokens[3] == "x1")) &&
+    if (((tokens.length == 3) || 
+        ((tokens.length == 4) && tokens[3] == "x1")) &&
         (tokens[0] == "LOGIN") &&
         (good_identifier?(tokens[1])))
       return true
         (tokens[0] == "LOGIN") &&
         (good_identifier?(tokens[1])))
       return true
@@ -313,7 +288,7 @@ class Login
 
   def Login.factory(str, player)
     (login, player.name, password, ext) = str.chomp.split
 
   def Login.factory(str, player)
     (login, player.name, password, ext) = str.chomp.split
-    if (ext)
+    if ext
       return Loginx1.new(player, password)
     else
       return LoginCSA.new(player, password)
       return Loginx1.new(player, password)
     else
       return LoginCSA.new(player, password)
@@ -401,6 +376,13 @@ 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
 
@@ -412,6 +394,9 @@ 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
   
   # Group in the rating system
   attr_accessor :rating_group
@@ -419,10 +404,8 @@ class BasicPlayer
   # 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
@@ -439,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(/@.*?$/, '')
@@ -463,28 +450,26 @@ 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
-    @write_queue = Queue::new
+    @socket_buffer = []
     @main_thread = Thread::current
     @main_thread = Thread::current
-    @writer_thread = Thread::start do
-      Thread.pass
-      writer()
-    end
+    @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))
@@ -499,8 +484,6 @@ 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))    
-      # TODO you should confirm that there is no message in the queue.
-      Thread::kill(@writer_thread) if @writer_thread
       begin
 #        @socket.close if (! @socket.closed?)
       rescue
       begin
 #        @socket.close if (! @socket.closed?)
       rescue
@@ -510,26 +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)
+    @mutex_write_guard.synchronize do
       begin
       begin
-        @socket.write(str)
+        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
       rescue Exception => ex
-        log_error("Failed to send a message to #{@name}.")
-        log_error("#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}")
-        return
+        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
   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)
@@ -543,29 +525,25 @@ class Player < BasicPlayer
   end
 
   def run(csa_1st_str=nil)
   end
 
   def run(csa_1st_str=nil)
-    while (csa_1st_str || (str = @socket.gets_safe(Default_Timeout)))
+    while ( csa_1st_str || 
+            str = gets_safe(@socket, (@socket_buffer.empty? ? Default_Timeout : 1)) )
+      $mutex.lock
       begin
       begin
-        $mutex.lock
-
-        if (@writer_thread == nil || @writer_thread.status == false)
-          # The writer_thread has been killed because of socket errors.
-          return
+        if (@game && @game.turn?(self))
+          @socket_buffer << str
+          str = @socket_buffer.shift
         end
         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
         str.chomp! if (str.class == String) # may be strip! ?
 
         if (@status == "finished")
           return
         end
         str.chomp! if (str.class == String) # may be strip! ?
-        log_message(str) if $DEBUG
         case str 
         when "" 
           # Application-level protocol for Keep-Alive
         case str 
         when "" 
           # Application-level protocol for Keep-Alive
@@ -578,10 +556,10 @@ class Player < BasicPlayer
             move = array_str.shift
             additional = array_str.shift
             if /^'(.*)/ =~ additional
             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)
+            @game.fh.print("#{Kconv.toeuc(comment.first)}\n") if (comment && comment.first && !s)
             return if (s && @protocol == LoginCSA::PROTOCOL)
           end
         when /^%[^%]/, :timeout
             return if (s && @protocol == LoginCSA::PROTOCOL)
           end
         when /^%[^%]/, :timeout
@@ -674,7 +652,7 @@ class Player < BasicPlayer
           rival = nil
           if (League::Floodgate.game_name?(game_name))
             if (my_sente_str != "*")
           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)
+              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
               next
             end
             @sente = nil
@@ -778,7 +756,8 @@ class Player < BasicPlayer
 end # class
 
 class Piece
 end # class
 
 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
@@ -1161,6 +1140,7 @@ class Board
     @gote_history  = Hash::new(0)
     @array = [[], [], [], [], [], [], [], [], [], []]
     @move_count = 0
     @gote_history  = Hash::new(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
@@ -1195,6 +1175,7 @@ class Board
     (1..9).each do |i|
       PieceFU::new(self, i, 7, true)
     end
     (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)
@@ -1233,6 +1214,7 @@ class Board
       @array[x0][y0].move_to(x1, y1)
     end
     @move_count += 1
       @array[x0][y0].move_to(x1, y1)
     end
     @move_count += 1
+    @teban = @teban ? false : true
     return true
   end
 
     return true
   end
 
@@ -1286,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
@@ -1296,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)
@@ -1316,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
@@ -1469,7 +1463,6 @@ class Board
       return :illegal
     end
     
       return :illegal
     end
     
-
     if (sg == "+")
       sente = true if sente == nil           # deprecated
       return :illegal unless sente == true   # black player's move must be black
     if (sg == "+")
       sente = true if sente == nil           # deprecated
       return :illegal unless sente == true   # black player's move must be black
@@ -1512,7 +1505,6 @@ class Board
     end
 
     move_to(x0, y0, x1, y1, name, sente)
     end
 
     move_to(x0, y0, x1, y1, name, sente)
-    str = to_s
 
     update_sennichite(sente)
     return :normal
 
     update_sennichite(sente)
     return :normal
@@ -1551,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
@@ -1577,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
@@ -1587,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
@@ -1603,26 +1601,25 @@ 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)
+                  LEAGUE.event, @game_name, 
+                  @sente.name, @gote.name, issue_current_time)
     @logfile = File.join(LEAGUE.dir, @id + ".csa")
 
     LEAGUE.games[@id] = self
     @logfile = File.join(LEAGUE.dir, @id + ".csa")
 
     LEAGUE.games[@id] = self
@@ -1645,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)
@@ -1661,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()
@@ -1693,81 +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_lose) || (move_status == :oute_sennichite_gote_lose))
-            @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 == :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
+    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
@@ -2087,8 +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.
+        --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
@@ -2122,9 +2128,9 @@ end
 
 def parse_command_line
   options = Hash::new
 
 def parse_command_line
   options = Hash::new
-  parser = GetoptLong.new( ["--daemon",         GetoptLong::REQUIRED_ARGUMENT],
-                           ["--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|
@@ -2140,7 +2146,7 @@ end
 
 def write_pid_file(file)
   open(file, "w") do |fh|
 
 def write_pid_file(file)
   open(file, "w") do |fh|
-    fh.print Process::pid, "\n"
+    fh.puts "#{$$}"
   end
 end
 
   end
 end
 
@@ -2162,6 +2168,42 @@ 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
 
   $mutex = Mutex::new
 def main
 
   $mutex = Mutex::new
@@ -2179,14 +2221,13 @@ 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"] || nil
+  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
   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)
+  $logger = WEBrick::Log.new(log_file) # thread safe
 
   LEAGUE.dir = dir || File.dirname(__FILE__)
   LEAGUE.setup_players_database
 
   LEAGUE.dir = dir || File.dirname(__FILE__)
   LEAGUE.setup_players_database
@@ -2195,6 +2236,15 @@ def main
   config[:Port]       = port
   config[:ServerType] = WEBrick::Daemon if $options["daemon"]
   config[:Logger]     = $logger
   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| 
 
   server = WEBrick::GenericServer.new(config)
   ["INT", "TERM"].each do |signal| 
@@ -2210,50 +2260,12 @@ def main
       # 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
       # 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 = nil
-      login  = nil
-      while (str = client.gets_timeout(ShogiServer::Login_Time))
-        begin
-          $mutex.lock
-          str =~ /([\r\n]*)$/
-          eol = $1
-          if (ShogiServer::Login::good_login?(str))
-            player = ShogiServer::Player::new(str, client)
-            player.eol = eol
-            login  = ShogiServer::Login::factory(str, player)
-            if (LEAGUE.players[player.name])
-              if ((LEAGUE.players[player.name].password == player.password) &&
-                  (LEAGUE.players[player.name].status != "game"))
-                log_message(sprintf("user %s login forcely", player.name))
-                LEAGUE.players[player.name].kill
-              else
-                login.incorrect_duplicated_player(str)
-                #Thread::exit
-                #return
-                # TODO
-                player = nil
-                break
-              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
-        next
-      end
+      player, login = login_loop(client) # loop
+      next unless player
+
       log_message(sprintf("user %s login", player.name))
       login.process
       log_message(sprintf("user %s login", player.name))
       login.process
-      player.run(login.csa_1st_str)
+      player.run(login.csa_1st_str) # loop
       begin
         $mutex.lock
         if (player.game)
       begin
         $mutex.lock
         if (player.game)
@@ -2273,7 +2285,7 @@ if ($0 == __FILE__)
   STDOUT.sync = true
   STDERR.sync = true
   TCPSocket.do_not_reverse_lookup = true
   STDOUT.sync = true
   STDERR.sync = true
   TCPSocket.do_not_reverse_lookup = true
-  Thread.abort_on_exception = true
+  Thread.abort_on_exception = $DEBUG ? true : false
 
   LEAGUE = ShogiServer::League::new
   main
 
   LEAGUE = ShogiServer::League::new
   main