OSDN Git Service

Improved a way to handle diferred moves (2008-03-24's change),
[shogi-server/shogi-server.git] / shogi-server
index 127efaf..5d33d6f 100755 (executable)
@@ -1,7 +1,8 @@
 #! /usr/bin/env ruby
 #! /usr/bin/env ruby
-## -*-Ruby-*- $RCSfile$ $Revision$ $Name$
+## $Id$
 
 
-## Copyright (C) 2004 nanami@2ch
+## 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
 
-DEFAULT_TIMEOUT = 10            # for single socket operation
-Total_Time = 1500
+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
+
+Default_Game_Name = "default-1500-0"
+
+One_Time = 10
 Least_Time_Per_Move = 1
 Least_Time_Per_Move = 1
-Watchdog_Time = 30              # time for ping
 Login_Time = 300                # time for LOGIN
 
 Release = "$Name$".split[1].sub(/\A[^\d]*/, '').gsub(/_/, '.')
 Release.concat("-") if (Release == "")
 Revision = "$Revision$".gsub(/[^\.\d]/, '')
 
 Login_Time = 300                # time for LOGIN
 
 Release = "$Name$".split[1].sub(/\A[^\d]*/, '').gsub(/_/, '.')
 Release.concat("-") if (Release == "")
 Revision = "$Revision$".gsub(/[^\.\d]/, '')
 
-STDOUT.sync = true
-STDERR.sync = true
+class League
 
 
-require 'getoptlong'
-require 'thread'
-require 'timeout'
-require 'socket'
-require 'ping'
+  class Floodgate
+    class << self
+      def game_name?(str)
+        return /^floodgate-\d+-\d+$/.match(str) ? true : false
+      end
+    end
 
 
-TCPSocket.do_not_reverse_lookup = true
+    def initialize(league)
+      @league = league
+      @next_time = nil
+      charge
+    end
 
 
-class TCPSocket
-  def gets_timeout(t = DEFAULT_TIMEOUT)
-    begin
-      timeout(t) do
-        return self.gets
+    def run
+      @thread = Thread.new do
+        Thread.pass
+        while (true)
+          begin
+            sleep(10)
+            next if Time.now < @next_time
+            @league.reload
+            match_game
+            charge
+          rescue Exception => ex 
+            # ignore errors
+            log_error("[in Floodgate's thread] #{ex}")
+          end
+        end
       end
       end
-    rescue TimeoutError
-      return nil
-    rescue
-      return nil
     end
     end
+
+    def shutdown
+      @thread.kill if @thread
+    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
+
+  def initialize
+    @mutex = Mutex.new # guard @players
+    @games = Hash::new
+    @players = Hash::new
+    @event = nil
+    @dir = File.dirname(__FILE__)
+    @floodgate = Floodgate.new(self)
+    @floodgate.run
   end
   end
-  def gets_safe(t = nil)
-    if (t && t > 0)
-      begin
-        timeout(t) do
-          return self.gets
+  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
+
+  def add(player)
+    self.load(player) if player.id
+    @mutex.synchronize do
+      @players[player.name] = player
+    end
+  end
+  
+  def delete(player)
+    @mutex.synchronize do
+      save(player)
+      @players.delete(player.name)
+    end
+  end
+
+  def reload
+    @mutex.synchronize do
+      @players.each {|player| load(player)}
+    end
+  end
+
+  def find_all_players
+    found = nil
+    @mutex.synchronize do
+      found = @players.find_all do |name, player|
+        yield player
+      end
+    end
+    return found.map {|a| a.last}
+  end
+  
+  def find(player_name)
+    found = nil
+    @mutex.synchronize do
+      found = @players[player_name]
+    end
+    return found
+  end
+
+  def get_player(status, game_name, sente, searcher)
+    found = nil
+    @mutex.synchronize do
+      found = @players.find do |name, player|
+        (player.status == status) &&
+        (player.game_name == game_name) &&
+        ( (sente == nil) || 
+          (player.sente == nil) || 
+          (player.sente == sente) ) &&
+        (player.name != searcher.name)
+      end
+    end
+    return found ? found.last : nil
+  end
+  
+  def load(player)
+    hash = search(player.id)
+    return unless hash
+
+    # a current user
+    player.name          = hash['name']
+    player.rate          = hash['rate'] || 0
+    player.modified_at   = hash['last_modified']
+    player.rating_group  = hash['rating_group']
+    player.win           = hash['win']  || 0
+    player.loss          = hash['loss'] || 0
+    player.last_game_win = hash['last_game_win'] || false
+  end
+
+  def save(player)
+    @db.transaction do
+      break unless  @db["players"]
+      @db["players"].each do |group, players|
+        hash = players[player.id]
+        if hash
+          hash['last_game_win'] = player.last_game_win
+          break
         end
         end
-      rescue TimeoutError
-        return :timeout
-      rescue
-        return nil
       end
       end
-    else
-      begin
-        return self.gets
-      rescue
-        return nil
+    end
+  end
+
+  def search(id)
+    hash = nil
+    @db.transaction(true) do
+      break unless  @db["players"]
+      @db["players"].each do |group, players|
+        hash = players[id]
+        break if hash
       end
     end
       end
     end
+    hash
   end
   end
-  def write_safe(str)
-    begin
-      return self.write(str)
-    rescue
-      return nil
+
+  def rated_players
+    players = []
+    @db.transaction(true) do
+      break unless  @db["players"]
+      @db["players"].each do |group, players_hash|
+        players << players_hash.keys
+      end
+    end
+    return players.flatten.collect do |id|
+      p = BasicPlayer.new
+      p.id = id
+      self.load(p)
+      p
     end
   end
 end
 
 
     end
   end
 end
 
 
-class League
-  def initialize
-    @games = Hash::new
-    @players = Hash::new
+######################################################
+# Processes the LOGIN command.
+#
+class Login
+  def Login.good_login?(str)
+    tokens = str.split
+    if (((tokens.length == 3) || 
+        ((tokens.length == 4) && tokens[3] == "x1")) &&
+        (tokens[0] == "LOGIN") &&
+        (good_identifier?(tokens[1])))
+      return true
+    else
+      return false
+    end
   end
   end
-  attr_accessor :players, :games
 
 
-  def add(player)
-    @players[player.name] = player
-  end
-  def delete(player)
-    @players.delete(player.name)
+  def Login.good_game_name?(str)
+    if ((str =~ /^(.+)-\d+-\d+$/) && (good_identifier?($1)))
+      return true
+    else
+      return false
+    end
   end
   end
-  def duplicated?(player)
-    if (@players[player.name])
+
+  def Login.good_identifier?(str)
+    if str =~ /\A[\w\d_@\-\.]{1,#{Max_Identifier_Length}}\z/
       return true
     else
       return false
     end
   end
       return true
     else
       return false
     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) &&
-          ((player.sente == nil) || (player.sente == sente)) &&
-          ((searcher == nil) || (player != searcher)))
-        return player
-      end
+
+  def Login.factory(str, player)
+    (login, player.name, password, ext) = str.chomp.split
+    if ext
+      return Loginx1.new(player, password)
+    else
+      return LoginCSA.new(player, password)
     end
     end
-    return nil
+  end
+
+  attr_reader :player
+  
+  # the first command that will be executed just after LOGIN.
+  # If it is nil, the default process will be started.
+  attr_reader :csa_1st_str
+
+  def initialize(player, password)
+    @player = player
+    @csa_1st_str = nil
+    parse_password(password)
+  end
+
+  def process
+    @player.write_safe(sprintf("LOGIN:%s OK\n", @player.name))
+    log_message(sprintf("user %s run in %s mode", @player.name, @player.protocol))
+  end
+
+  def incorrect_duplicated_player(str)
+    @player.write_safe("LOGIN:incorrect\n")
+    @player.write_safe(sprintf("username %s is already connected\n", @player.name)) if (str.split.length >= 4)
+    sleep 3 # wait for sending the above messages.
+    @player.name = "%s [duplicated]" % [@player.name]
+    @player.finish
   end
 end
 
   end
 end
 
-class Player
-  def initialize(str, socket)
+######################################################
+# Processes LOGIN for the CSA standard mode.
+#
+class LoginCSA < Login
+  PROTOCOL = "CSA"
+
+  def initialize(player, password)
+    @gamename = nil
+    super
+    @player.protocol = PROTOCOL
+  end
+
+  def parse_password(password)
+    if Login.good_game_name?(password)
+      @gamename = password
+      @player.set_password(nil)
+    elsif password.split(",").size > 1
+      @gamename, *trip = password.split(",")
+      @player.set_password(trip.join(","))
+    else
+      @player.set_password(password)
+      @gamename = Default_Game_Name
+    end
+    @gamename = self.class.good_game_name?(@gamename) ? @gamename : Default_Game_Name
+  end
+
+  def process
+    super
+    @csa_1st_str = "%%GAME #{@gamename} *"
+  end
+end
+
+######################################################
+# Processes LOGIN for the extented mode.
+#
+class Loginx1 < Login
+  PROTOCOL = "x1"
+
+  def initialize(player, password)
+    super
+    @player.protocol = PROTOCOL
+  end
+  
+  def parse_password(password)
+    @player.set_password(password)
+  end
+
+  def process
+    super
+    @player.write_safe(sprintf("##[LOGIN] +OK %s\n", PROTOCOL))
+  end
+end
+
+
+class BasicPlayer
+  def initialize
+    @id = nil
     @name = nil
     @password = nil
     @name = nil
     @password = nil
+    @last_game_win = false
+  end
+
+  # Idetifier of the player in the rating system
+  attr_accessor :id
+
+  # Name of the player
+  attr_accessor :name
+  
+  # Password of the player, which does not include a trip
+  attr_accessor :password
+
+  # Score in the rating sysem
+  attr_accessor :rate
+
+  # Number of games for win and loss in the rating system
+  attr_accessor :win, :loss
+  
+  # Group in the rating system
+  attr_accessor :rating_group
+
+  # Last timestamp when the rate was modified
+  attr_accessor :modified_at
+
+  # Whether win the previous game or not
+  attr_accessor :last_game_win
+
+  def modified_at
+    @modified_at || Time.now
+  end
+
+  def rate=(new_rate)
+    if @rate != new_rate
+      @rate = new_rate
+      @modified_at = Time.now
+    end
+  end
+
+  def rated?
+    @id != nil
+  end
+
+  def last_game_win?
+    return @last_game_win
+  end
+
+  def simple_id
+    if @trip
+      simple_name = @name.gsub(/@.*?$/, '')
+      "%s+%s" % [simple_name, @trip[0..8]]
+    else
+      @name
+    end
+  end
+
+  ##
+  # Parses str in the LOGIN command, sets up @id and @trip
+  #
+  def set_password(str)
+    if str && !str.empty?
+      @password = str.strip
+      @id   = "%s+%s" % [@name, Digest::MD5.hexdigest(@password)]
+    else
+      @id = @password = nil
+    end
+  end
+end
+
+
+class Player < BasicPlayer
+  def initialize(str, socket, eol=nil)
+    super()
     @socket = socket
     @socket = socket
-    @status = "connected"        # game_waiting -> agree_waiting -> start_waiting -> game
+    @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 = ""
     @game = nil
     @game_name = ""
-    @mytime = Total_Time        # set in start method also
+    @mytime = 0                 # set in start method also
     @sente = nil
     @sente = nil
-    @watchdog_thread = nil
-
-    login(str)
+    @socket_buffer = []
+    @main_thread = Thread::current
+    @mutex_write_guard = Mutex.new
   end
 
   end
 
-  attr_accessor :name, :password, :socket, :status
-  attr_accessor :protocol, :eol, :game, :mytime, :watchdog_thread, :game_name, :sente
+  attr_accessor :socket, :status
+  attr_accessor :protocol, :eol, :game, :mytime, :game_name, :sente
+  attr_accessor :main_thread
+  attr_reader :socket_buffer
+  
+  def kill
+    log_message(sprintf("user %s killed", @name))
+    if (@game)
+      @game.kill(self)
+    end
+    finish
+    Thread::kill(@main_thread) if @main_thread
+  end
 
   def finish
 
   def finish
-    log_message(sprintf("user %s finish", @name))    
-    Thread::kill(@watchdog_thread) if @watchdog_thread
-    @socket.close if (! @socket.closed?)
+    if (@status != "finished")
+      @status = "finished"
+      log_message(sprintf("user %s finish", @name))    
+      begin
+#        @socket.close if (! @socket.closed?)
+      rescue
+        log_message(sprintf("user %s finish failed", @name))    
+      end
+    end
   end
 
   end
 
-  def watchdog(time)
-    while true
+  def write_safe(str)
+    @mutex_write_guard.synchronize do
       begin
       begin
-        Ping.pingecho(@socket.addr[3])
-      rescue
+        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
-      sleep(time)
     end
   end
 
   def to_s
     end
   end
 
   def to_s
-    if ((status == "game_waiting") ||
-        (status == "agree_waiting") ||
-        (status == "game"))
+    if ["game_waiting", "start_waiting", "agree_waiting", "game"].include?(status)
       if (@sente)
         return sprintf("%s %s %s %s +", @name, @protocol, @status, @game_name)
       elsif (@sente == false)
         return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name)
       elsif (@sente == nil)
       if (@sente)
         return sprintf("%s %s %s %s +", @name, @protocol, @status, @game_name)
       elsif (@sente == false)
         return sprintf("%s %s %s %s -", @name, @protocol, @status, @game_name)
       elsif (@sente == nil)
-        return sprintf("%s %s %s %s +-", @name, @protocol, @status, @game_name)
+        return sprintf("%s %s %s %s *", @name, @protocol, @status, @game_name)
       end
     else
       return sprintf("%s %s %s", @name, @protocol, @status)
     end
   end
 
       end
     else
       return sprintf("%s %s %s", @name, @protocol, @status)
     end
   end
 
-  def write_help
-    @socket.write_safe('##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"')
-  end
-
-  def write_safe(str)
-    @socket.write_safe(str.gsub(/[\r\n]+/, @eol))
-  end
-
-  def login(str)
-    str =~ /([\r\n]*)$/
-    @eol = $1
-    str.chomp!
-    (login, @name, @password, ext) = str.split
-    if (ext)
-      @protocol = "x1"
-    else
-      @protocol = "CSA"
-    end
-    @watchdog_thread = Thread::start do
-      watchdog(Watchdog_Time)
-    end
-  end
-    
-  def run
-    write_safe(sprintf("LOGIN:%s OK\n", @name))
-    if (@protocol != "CSA")
-      log_message(sprintf("user %s run in %s mode", @name, @protocol))
-      write_safe(sprintf("##[LOGIN] +OK %s\n", @protocol))
-    else
-      log_message(sprintf("user %s run in CSA mode", @name))
-      csa_1st_str = "%%GAME default +-"
-    end
-
-    
-    while (csa_1st_str || (str = @socket.gets_safe(@mytime)))
+  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
-        str.chomp! if (str.class == String)
-        case str
-        when /^[\+\-%][^%]/, :timeout
+
+        if (@status == "finished")
+          return
+        end
+        str.chomp! if (str.class == String) # may be strip! ?
+        case str 
+        when "" 
+          # Application-level protocol for Keep-Alive
+          # If the server gets LF, it sends back LF.
+          # 30 sec rule (client may not send LF again within 30 sec) is not implemented yet.
+          write_safe("\n")
+        when /^[\+\-][^%]/
+          if (@status == "game")
+            array_str = str.split(",")
+            move = array_str.shift
+            additional = array_str.shift
+            if /^'(.*)/ =~ additional
+              comment = array_str.unshift("'*#{$1.toeuc}")
+            end
+            s = @game.handle_one_move(move, self)
+            @game.fh.print("#{Kconv.toeuc(comment.first)}\n") if (comment && comment.first && !s)
+            return if (s && @protocol == LoginCSA::PROTOCOL)
+          end
+        when /^%[^%]/, :timeout
           if (@status == "game")
             s = @game.handle_one_move(str, self)
           if (@status == "game")
             s = @game.handle_one_move(str, self)
-            return if (s && @protocol == "CSA")
+            return if (s && @protocol == LoginCSA::PROTOCOL)
+          # else
+          #   begin
+          #     @socket.write("##[KEEPALIVE] #{Time.now}\n")
+          #   rescue Exception => ex
+          #     log_error("Failed to send a keepalive to #{@name}.")
+          #     log_error("#{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}")
+          #     return
+          #   end
+          end
+        when :exception
+          log_error("Failed to receive a message from #{@name}.")
+          return
+        when /^REJECT/
+          if (@status == "agree_waiting")
+            @game.reject(@name)
+            return if (@protocol == LoginCSA::PROTOCOL)
           else
           else
-            next
+            write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
           end
         when /^AGREE/
           if (@status == "agree_waiting")
           end
         when /^AGREE/
           if (@status == "agree_waiting")
@@ -225,60 +595,128 @@ class Player
               @game.gote.status = "game"
             end
           else
               @game.gote.status = "game"
             end
           else
-            write_safe("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status)
-            next
+            write_safe(sprintf("##[ERROR] you are in %s status. AGREE is valid in agree_waiting status\n", @status))
+          end
+        when /^%%SHOW\s+(\S+)/
+          game_id = $1
+          if (LEAGUE.games[game_id])
+            write_safe(LEAGUE.games[game_id].show.gsub(/^/, '##[SHOW] '))
+          end
+          write_safe("##[SHOW] +OK\n")
+        when /^%%MONITORON\s+(\S+)/
+          game_id = $1
+          if (LEAGUE.games[game_id])
+            LEAGUE.games[game_id].monitoron(self)
+            write_safe(LEAGUE.games[game_id].show.gsub(/^/, "##[MONITOR][#{game_id}] "))
+            write_safe("##[MONITOR][#{game_id}] +OK\n")
+          end
+        when /^%%MONITOROFF\s+(\S+)/
+          game_id = $1
+          if (LEAGUE.games[game_id])
+            LEAGUE.games[game_id].monitoroff(self)
           end
         when /^%%HELP/
           end
         when /^%%HELP/
-          write_help
-        when /^%%GAME\s+(\S+)\s+([\+\-]+)/
+          write_safe(
+            %!##[HELP] available commands "%%WHO", "%%CHAT str", "%%GAME game_name +", "%%GAME game_name -"\n!)
+        when /^%%RATING/
+          players = LEAGUE.rated_players
+          players.sort {|a,b| b.rate <=> a.rate}.each do |p|
+            write_safe("##[RATING] %s \t %4d @%s\n" % 
+                       [p.simple_id, p.rate, p.modified_at.strftime("%Y-%m-%d")])
+          end
+          write_safe("##[RATING] +OK\n")
+        when /^%%VERSION/
+          write_safe "##[VERSION] Shogi Server revision #{Revision}\n"
+          write_safe("##[VERSION] +OK\n")
+        when /^%%GAME\s*$/
           if ((@status == "connected") || (@status == "game_waiting"))
           if ((@status == "connected") || (@status == "game_waiting"))
-            @status = "game_waiting"
+            @status = "connected"
+            @game_name = ""
           else
             write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
           else
             write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
-            next
           end
           end
-          @status = "game_waiting"
-          @game_name = $1
-          sente_str = $2
-          if (sente_str == "+")
-            @sente = true
-            rival_sente = false
-          elsif (sente_str == "-")
-            @sente = false
-            rival_sente = true
+        when /^%%(GAME|CHALLENGE)\s+(\S+)\s+([\+\-\*])\s*$/
+          command_name = $1
+          game_name = $2
+          my_sente_str = $3
+          if (! Login::good_game_name?(game_name))
+            write_safe(sprintf("##[ERROR] bad game name\n"))
+            next
+          elsif ((@status == "connected") || (@status == "game_waiting"))
+            ## continue
           else
           else
+            write_safe(sprintf("##[ERROR] you are in %s status. GAME is valid in connected or game_waiting status\n", @status))
+            next
+          end
+
+          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
             @sente = nil
-            rival_sente = nil
+          else
+            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
-          rival = LEAGUE.get_player("game_waiting", @game_name, rival_sente, self)
-          rival = LEAGUE.get_player("game_waiting", @game_name, nil, self) if (! rival)
+
           if (rival)
           if (rival)
-            if (@sente == nil)
+            @game_name = game_name
+            if ((my_sente_str == "*") && (rival.sente == nil))
               if (rand(2) == 0)
                 @sente = true
               if (rand(2) == 0)
                 @sente = true
-                rival_sente = false
+                rival.sente = false
               else
                 @sente = false
               else
                 @sente = false
-                rival_sente = true
+                rival.sente = true
               end
               end
-            elsif (rival_sente == nil)
-              if (@sente)
-                rival_sente = false
+            elsif (rival.sente == true) # rival has higher priority
+              @sente = false
+            elsif (rival.sente == false)
+              @sente = true
+            elsif (my_sente_str == "+")
+              @sente = true
+              rival.sente = false
+            elsif (my_sente_str == "-")
+              @sente = false
+              rival.sente = true
+            else
+              ## never reached
+            end
+            Game::new(@game_name, self, rival)
+          else # rival not found
+            if (command_name == "GAME")
+              @status = "game_waiting"
+              @game_name = game_name
+              if (my_sente_str == "+")
+                @sente = true
+              elsif (my_sente_str == "-")
+                @sente = false
               else
               else
-                rival_sente = true
+                @sente = nil
               end
               end
+            else                # challenge
+              write_safe(sprintf("##[ERROR] can't find rival for %s\n", game_name))
+              @status = "connected"
+              @game_name = ""
+              @sente = nil
             end
             end
-            rival.sente = rival_sente
-            Game::new(@game_name, self, rival)
-            self.status = "agree_waiting"
-            rival.status = "agree_waiting"
           end
         when /^%%CHAT\s+(.+)/
           message = $1
           LEAGUE.players.each do |name, player|
           end
         when /^%%CHAT\s+(.+)/
           message = $1
           LEAGUE.players.each do |name, player|
-            if (player.protocol != "CSA")
-              s = player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message)) 
-              player.status = "zombie" if (! s)
+            if (player.protocol != LoginCSA::PROTOCOL)
+              player.write_safe(sprintf("##[CHAT][%s] %s\n", @name, message)) 
             end
           end
         when /^%%LIST/
             end
           end
         when /^%%LIST/
@@ -288,12 +726,6 @@ class Player
           end
           buf.push("##[LIST] +OK\n")
           write_safe(buf.join)
           end
           buf.push("##[LIST] +OK\n")
           write_safe(buf.join)
-        when /^%%SHOW\s+(\S+)/
-          id = $1
-          if (LEAGUE.games[id])
-            write_safe(LEAGUE.games[id].board.to_s.gsub(/^/, '##[SHOW] '))
-          end
-          write_safe("##[SHOW] +OK\n")
         when /^%%WHO/
           buf = Array::new
           LEAGUE.players.each do |name, player|
         when /^%%WHO/
           buf = Array::new
           LEAGUE.players.each do |name, player|
@@ -302,153 +734,780 @@ class Player
           buf.push("##[WHO] +OK\n")
           write_safe(buf.join)
         when /^LOGOUT/
           buf.push("##[WHO] +OK\n")
           write_safe(buf.join)
         when /^LOGOUT/
+          @status = "connected"
           write_safe("LOGOUT:completed\n")
           write_safe("LOGOUT:completed\n")
-          finish
           return
           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
         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"}
-  def initialize(name, sente)
-    @name = name
+  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
+    @y = y
     @sente = sente
     @sente = sente
-    @promoted = false
+    @promoted = promoted
+
+    if ((x == 0) || (y == 0))
+      if (sente)
+        hands = board.sente_hands
+      else
+        hands = board.gote_hands
+      end
+      hands.push(self)
+      hands.sort! {|a, b|
+        a.name <=> b.name
+      }
+    else
+      @board.array[x][y] = self
+    end
+  end
+  attr_accessor :promoted, :sente, :x, :y, :board
+
+  def room_of_head?(x, y, name)
+    true
+  end
+
+  def movable_grids
+    return adjacent_movable_grids + far_movable_grids
+  end
+
+  def far_movable_grids
+    return []
+  end
+
+  def jump_to?(x, y)
+    if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
+      if ((@board.array[x][y] == nil) || # dst is empty
+          (@board.array[x][y].sente != @sente)) # dst is enemy
+        return true
+      end
+    end
+    return false
+  end
+
+  def put_to?(x, y)
+    if ((1 <= x) && (x <= 9) && (1 <= y) && (y <= 9))
+      if (@board.array[x][y] == nil) # dst is empty?
+        return true
+      end
+    end
+    return false
+  end
+
+  def adjacent_movable_grids
+    grids = Array::new
+    if (@promoted)
+      moves = @promoted_moves
+    else
+      moves = @normal_moves
+    end
+    moves.each do |(dx, dy)|
+      if (@sente)
+        cand_y = @y - dy
+      else
+        cand_y = @y + dy
+      end
+      cand_x = @x + dx
+      if (jump_to?(cand_x, cand_y))
+        grids.push([cand_x, cand_y])
+      end
+    end
+    return grids
+  end
+
+  def move_to?(x, y, name)
+    return false if (! room_of_head?(x, y, name))
+    return false if ((name != @name) && (name != @promoted_name))
+    return false if (@promoted && (name != @promoted_name)) # can't un-promote
+
+    if (! @promoted)
+      return false if (((@x == 0) || (@y == 0)) && (name != @name)) # can't put promoted piece
+      if (@sente)
+        return false if ((4 <= @y) && (4 <= y) && (name != @name)) # can't promote
+      else
+        return false if ((6 >= @y) && (6 >= y) && (name != @name))
+      end
+    end
+
+    if ((@x == 0) || (@y == 0))
+      return jump_to?(x, y)
+    else
+      return movable_grids.include?([x, y])
+    end
+  end
+
+  def move_to(x, y)
+    if ((@x == 0) || (@y == 0))
+      if (@sente)
+        @board.sente_hands.delete(self)
+      else
+        @board.gote_hands.delete(self)
+      end
+      @board.array[x][y] = self
+    elsif ((x == 0) || (y == 0))
+      @promoted = false         # clear promoted flag before moving to hands
+      if (@sente)
+        @board.sente_hands.push(self)
+      else
+        @board.gote_hands.push(self)
+      end
+      @board.array[@x][@y] = nil
+    else
+      @board.array[@x][@y] = nil
+      @board.array[x][y] = self
+    end
+    @x = x
+    @y = y
+  end
+
+  def point
+    @point
+  end
+
+  def name
+    @name
   end
   end
-  attr_accessor :name, :promoted, :sente
 
   def promoted_name
 
   def promoted_name
-    PROMOTE[name]
+    @promoted_name
+  end
+
+  def to_s
+    if (@sente)
+      sg = "+"
+    else
+      sg = "-"
+    end
+    if (@promoted)
+      n = @promoted_name
+    else
+      n = @name
+    end
+    return sg + n
+  end
+end
+
+class PieceFU < Piece
+  def initialize(*arg)
+    @point = 1
+    @normal_moves = [[0, +1]]
+    @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
+    @name = "FU"
+    @promoted_name = "TO"
+    super
+  end
+  def room_of_head?(x, y, name)
+    if (name == "FU")
+      if (@sente)
+        return false if (y == 1)
+      else
+        return false if (y == 9)
+      end
+      ## 2fu check
+      c = 0
+      iy = 1
+      while (iy <= 9)
+        if ((iy  != @y) &&      # not source position
+            @board.array[x][iy] &&
+            (@board.array[x][iy].sente == @sente) && # mine
+            (@board.array[x][iy].name == "FU") &&
+            (@board.array[x][iy].promoted == false))
+          return false
+        end
+        iy = iy + 1
+      end
+    end
+    return true
+  end
+end
+
+class PieceKY  < Piece
+  def initialize(*arg)
+    @point = 1
+    @normal_moves = []
+    @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
+    @name = "KY"
+    @promoted_name = "NY"
+    super
+  end
+  def room_of_head?(x, y, name)
+    if (name == "KY")
+      if (@sente)
+        return false if (y == 1)
+      else
+        return false if (y == 9)
+      end
+    end
+    return true
+  end
+  def far_movable_grids
+    grids = Array::new
+    if (@promoted)
+      return []
+    else
+      if (@sente)                 # up
+        cand_x = @x
+        cand_y = @y - 1
+        while (jump_to?(cand_x, cand_y))
+          grids.push([cand_x, cand_y])
+          break if (! put_to?(cand_x, cand_y))
+          cand_y = cand_y - 1
+        end
+      else                        # down
+        cand_x = @x
+        cand_y = @y + 1
+        while (jump_to?(cand_x, cand_y))
+          grids.push([cand_x, cand_y])
+          break if (! put_to?(cand_x, cand_y))
+          cand_y = cand_y + 1
+        end
+      end
+      return grids
+    end
+  end
+end
+class PieceKE  < Piece
+  def initialize(*arg)
+    @point = 1
+    @normal_moves = [[+1, +2], [-1, +2]]
+    @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
+    @name = "KE"
+    @promoted_name = "NK"
+    super
+  end
+  def room_of_head?(x, y, name)
+    if (name == "KE")
+      if (@sente)
+        return false if ((y == 1) || (y == 2))
+      else
+        return false if ((y == 9) || (y == 8))
+      end
+    end
+    return true
+  end
+end
+class PieceGI  < Piece
+  def initialize(*arg)
+    @point = 1
+    @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, -1], [-1, -1]]
+    @promoted_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
+    @name = "GI"
+    @promoted_name = "NG"
+    super
+  end
+end
+class PieceKI  < Piece
+  def initialize(*arg)
+    @point = 1
+    @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1]]
+    @promoted_moves = []
+    @name = "KI"
+    @promoted_name = nil
+    super
+  end
+end
+class PieceKA  < Piece
+  def initialize(*arg)
+    @point = 5
+    @normal_moves = []
+    @promoted_moves = [[0, +1], [+1, 0], [-1, 0], [0, -1]]
+    @name = "KA"
+    @promoted_name = "UM"
+    super
+  end
+  def far_movable_grids
+    grids = Array::new
+    ## up right
+    cand_x = @x - 1
+    cand_y = @y - 1
+    while (jump_to?(cand_x, cand_y))
+      grids.push([cand_x, cand_y])
+      break if (! put_to?(cand_x, cand_y))
+      cand_x = cand_x - 1
+      cand_y = cand_y - 1
+    end
+    ## down right
+    cand_x = @x - 1
+    cand_y = @y + 1
+    while (jump_to?(cand_x, cand_y))
+      grids.push([cand_x, cand_y])
+      break if (! put_to?(cand_x, cand_y))
+      cand_x = cand_x - 1
+      cand_y = cand_y + 1
+    end
+    ## up left
+    cand_x = @x + 1
+    cand_y = @y - 1
+    while (jump_to?(cand_x, cand_y))
+      grids.push([cand_x, cand_y])
+      break if (! put_to?(cand_x, cand_y))
+      cand_x = cand_x + 1
+      cand_y = cand_y - 1
+    end
+    ## down left
+    cand_x = @x + 1
+    cand_y = @y + 1
+    while (jump_to?(cand_x, cand_y))
+      grids.push([cand_x, cand_y])
+      break if (! put_to?(cand_x, cand_y))
+      cand_x = cand_x + 1
+      cand_y = cand_y + 1
+    end
+    return grids
+  end
+end
+class PieceHI  < Piece
+  def initialize(*arg)
+    @point = 5
+    @normal_moves = []
+    @promoted_moves = [[+1, +1], [-1, +1], [+1, -1], [-1, -1]]
+    @name = "HI"
+    @promoted_name = "RY"
+    super
+  end
+  def far_movable_grids
+    grids = Array::new
+    ## up
+    cand_x = @x
+    cand_y = @y - 1
+    while (jump_to?(cand_x, cand_y))
+      grids.push([cand_x, cand_y])
+      break if (! put_to?(cand_x, cand_y))
+      cand_y = cand_y - 1
+    end
+    ## down
+    cand_x = @x
+    cand_y = @y + 1
+    while (jump_to?(cand_x, cand_y))
+      grids.push([cand_x, cand_y])
+      break if (! put_to?(cand_x, cand_y))
+      cand_y = cand_y + 1
+    end
+    ## right
+    cand_x = @x - 1
+    cand_y = @y
+    while (jump_to?(cand_x, cand_y))
+      grids.push([cand_x, cand_y])
+      break if (! put_to?(cand_x, cand_y))
+      cand_x = cand_x - 1
+    end
+    ## down
+    cand_x = @x + 1
+    cand_y = @y
+    while (jump_to?(cand_x, cand_y))
+      grids.push([cand_x, cand_y])
+      break if (! put_to?(cand_x, cand_y))
+      cand_x = cand_x + 1
+    end
+    return grids
+  end
+end
+class PieceOU < Piece
+  def initialize(*arg)
+    @point = 0
+    @normal_moves = [[0, +1], [+1, +1], [-1, +1], [+1, +0], [-1, +0], [0, -1], [+1, -1], [-1, -1]]
+    @promoted_moves = []
+    @name = "OU"
+    @promoted_name = nil
+    super
+  end
+end
+
+class Board
+  def initialize
+    @sente_hands = Array::new
+    @gote_hands  = Array::new
+    @history       = Hash::new(0)
+    @sente_history = Hash::new(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
+
+  def initial
+    PieceKY::new(self, 1, 1, false)
+    PieceKE::new(self, 2, 1, false)
+    PieceGI::new(self, 3, 1, false)
+    PieceKI::new(self, 4, 1, false)
+    PieceOU::new(self, 5, 1, false)
+    PieceKI::new(self, 6, 1, false)
+    PieceGI::new(self, 7, 1, false)
+    PieceKE::new(self, 8, 1, false)
+    PieceKY::new(self, 9, 1, false)
+    PieceKA::new(self, 2, 2, false)
+    PieceHI::new(self, 8, 2, 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)
+    PieceGI::new(self, 3, 9, true)
+    PieceKI::new(self, 4, 9, true)
+    PieceOU::new(self, 5, 9, true)
+    PieceKI::new(self, 6, 9, true)
+    PieceGI::new(self, 7, 9, true)
+    PieceKE::new(self, 8, 9, true)
+    PieceKY::new(self, 9, 9, true)
+    PieceKA::new(self, 8, 8, true)
+    PieceHI::new(self, 2, 8, true)
+    (1..9).each do |i|
+      PieceFU::new(self, i, 7, true)
+    end
+    @teban = true
+  end
+
+  def have_piece?(hands, name)
+    piece = hands.find { |i|
+      i.name == name
+    }
+    return piece
+  end
+
+  def move_to(x0, y0, x1, y1, name, sente)
+    if (sente)
+      hands = @sente_hands
+    else
+      hands = @gote_hands
+    end
+
+    if ((x0 == 0) || (y0 == 0))
+      piece = have_piece?(hands, name)
+      return :illegal if (! piece.move_to?(x1, y1, name)) # TODO null check for the piece?
+      piece.move_to(x1, y1)
+    else
+      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[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)
+        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
+    @teban = @teban ? false : true
+    return true
+  end
+
+  def look_for_ou(sente)
+    x = 1
+    while (x <= 9)
+      y = 1
+      while (y <= 9)
+        if (@array[x][y] &&
+            (@array[x][y].name == "OU") &&
+            (@array[x][y].sente == sente))
+          return @array[x][y]
+        end
+        y = y + 1
+      end
+      x = x + 1
+    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
+    while (x <= 9)
+      y = 1
+      while (y <= 9)
+        if (@array[x][y] &&
+            (@array[x][y].sente != sente))
+          if (@array[x][y].movable_grids.include?([ou.x, ou.y]))
+            return true
+          end
+        end
+        y = y + 1
+      end
+      x = x + 1
+    end
+    return false
+  end
+
+  def uchifuzume?(sente)
+    rival_ou = look_for_ou(! sente)   # rival's ou
+    if (sente)                  # rival is gote
+      if ((rival_ou.y != 9) &&
+          (@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
+        fu_x = rival_ou.x
+        fu_y = rival_ou.y + 1
+      else
+        return false
+      end
+    else                        # gote
+      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
+        fu_x = rival_ou.x
+        fu_y = rival_ou.y - 1
+      else
+        return false
+      end
+    end
+
+    ## case: rival_ou is moving
+    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)
+      raise "internal error" if (s != true)
+      if (! tmp_board.checkmated?(! sente)) # good move
+        return false
+      end
+    end
+
+    ## case: rival is capturing fu
+    x = 1
+    while (x <= 9)
+      y = 1
+      while (y <= 9)
+        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)
+            names << @array[x][y].promoted_name
+          else
+            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
+          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
+          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
+      x = x + 1
+    end
+    return true
   end
 
   end
 
-  def to_s
-    if (@sente)
-      sg = "+"
+  # @[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
+        @gote_history["dummy"]  = 1  # flag to see Gote player is checking Sente player
+      end
     else
     else
-      sg = "-"
+      if (player)
+        @sente_history.clear # no more continuous check
+      else
+        @gote_history.clear  # no more continuous check
+      end
     end
     end
-    if (@promoted)
-      n = PROMOTE[@name]
+    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
     else
-      n = @name
+      return nil
     end
     end
-    return sg + n
   end
   end
-end
 
 
+  def sennichite?(sente)
+    if (@history[to_s] >= 4) # already 3 times
+      return true
+    end
+    return false
+  end
 
 
+  def good_kachi?(sente)
+    if (checkmated?(sente))
+      puts "'NG: Checkmating." if $DEBUG
+      return false 
+    end
+    
+    ou = look_for_ou(sente)
+    if (sente && (ou.y >= 4))
+      puts "'NG: Black's OU does not enter yet." if $DEBUG
+      return false     
+    end  
+    if (! sente && (ou.y <= 6))
+      puts "'NG: White's OU does not enter yet." if $DEBUG
+      return false 
+    end
+      
+    number = 0
+    point = 0
 
 
-class Board
-  def initialize
-    @sente_hands = Array::new
-    @gote_hands = Array::new
-    @array = [[], [], [], [], [], [], [], [], [], []]
-  end
-  attr_accessor :array, :sente_hands, :gote_hands
+    if (sente)
+      hands = @sente_hands
+      r = [1, 2, 3]
+    else
+      hands = @gote_hands
+      r = [7, 8, 9]
+    end
+    r.each do |y|
+      x = 1
+      while (x <= 9)
+        if (@array[x][y] &&
+            (@array[x][y].sente == sente) &&
+            (@array[x][y].point > 0))
+          point = point + @array[x][y].point
+          number = number + 1
+        end
+        x = x + 1
+      end
+    end
+    hands.each do |piece|
+      point = point + piece.point
+    end
 
 
-  def initial
-    @array[1][1] = Piece::new("KY", false)
-    @array[2][1] = Piece::new("KE", false)
-    @array[3][1] = Piece::new("GI", false)
-    @array[4][1] = Piece::new("KI", false)
-    @array[5][1] = Piece::new("OU", false)
-    @array[6][1] = Piece::new("KI", false)
-    @array[7][1] = Piece::new("GI", false)
-    @array[8][1] = Piece::new("KE", false)
-    @array[9][1] = Piece::new("KY", false)
-    @array[2][2] = Piece::new("KA", false)
-    @array[8][2] = Piece::new("HI", false)
-    @array[1][3] = Piece::new("FU", false)
-    @array[2][3] = Piece::new("FU", false)
-    @array[3][3] = Piece::new("FU", false)
-    @array[4][3] = Piece::new("FU", false)
-    @array[5][3] = Piece::new("FU", false)
-    @array[6][3] = Piece::new("FU", false)
-    @array[7][3] = Piece::new("FU", false)
-    @array[8][3] = Piece::new("FU", false)
-    @array[9][3] = Piece::new("FU", false)
-
-    @array[1][9] = Piece::new("KY", true)
-    @array[2][9] = Piece::new("KE", true)
-    @array[3][9] = Piece::new("GI", true)
-    @array[4][9] = Piece::new("KI", true)
-    @array[5][9] = Piece::new("OU", true)
-    @array[6][9] = Piece::new("KI", true)
-    @array[7][9] = Piece::new("GI", true)
-    @array[8][9] = Piece::new("KE", true)
-    @array[9][9] = Piece::new("KY", true)
-    @array[2][8] = Piece::new("HI", true)
-    @array[8][8] = Piece::new("KA", true)
-    @array[1][7] = Piece::new("FU", true)
-    @array[2][7] = Piece::new("FU", true)
-    @array[3][7] = Piece::new("FU", true)
-    @array[4][7] = Piece::new("FU", true)
-    @array[5][7] = Piece::new("FU", true)
-    @array[6][7] = Piece::new("FU", true)
-    @array[7][7] = Piece::new("FU", true)
-    @array[8][7] = Piece::new("FU", true)
-    @array[9][7] = Piece::new("FU", true)
-  end
-
-  def get_piece_from_hands(hands, name)
-    p = hands.find { |i|
-      i.name == name
-    }
-    if (p)
-      hands.delete(p)
+    if (number < 10)
+      puts "'NG: Piece#[%d] is too small." % [number] if $DEBUG
+      return false     
+    end  
+    if (sente)
+      if (point < 28)
+        puts "'NG: Black's point#[%d] is too small." % [point] if $DEBUG
+        return false 
+      end  
+    else
+      if (point < 27)
+        puts "'NG: White's point#[%d] is too small." % [point] if $DEBUG
+        return false 
+      end
     end
     end
-    return p
+
+    puts "'Good: Piece#[%d], Point[%d]." % [number, point] if $DEBUG
+    return true
   end
 
   end
 
-  def handle_one_move(str)
+  # sente is nil only if tests in test_board run
+  def handle_one_move(str, sente=nil)
     if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
     if (str =~ /^([\+\-])(\d)(\d)(\d)(\d)([A-Z]{2})/)
-      p = $1
+      sg = $1
       x0 = $2.to_i
       y0 = $3.to_i
       x1 = $4.to_i
       y1 = $5.to_i
       name = $6
       x0 = $2.to_i
       y0 = $3.to_i
       x1 = $4.to_i
       y1 = $5.to_i
       name = $6
-    elsif (str =~ /^%/)
-      return true
+    elsif (str =~ /^%KACHI/)
+      raise ArgumentError, "sente is null", caller if sente == nil
+      if (good_kachi?(sente))
+        return :kachi_win
+      else
+        return :kachi_lose
+      end
+    elsif (str =~ /^%TORYO/)
+      return :toryo
     else
     else
-      return false              # illegal move
+      return :illegal
+    end
+    
+    if (((x0 == 0) || (y0 == 0)) && # source is not from hand
+        ((x0 != 0) || (y0 != 0)))
+      return :illegal
+    elsif ((x1 == 0) || (y1 == 0)) # destination is out of board
+      return :illegal
     end
     end
-    if (p == "+")
-      sente = true
+    
+    if (sg == "+")
+      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
-    if (@array[x1][y1])
-      if (@array[x1][y1] == sente) # this is mine
-        return false            
-      end
-      hands.push(@array[x1][y1])
-      @array[x1][y1] = nil
-    end
+    
+    ## source check
     if ((x0 == 0) && (y0 == 0))
     if ((x0 == 0) && (y0 == 0))
-      p = get_piece_from_hands(hands, name)
-      return false if (! p)     # i don't have this one
-      @array[x1][y1] = p
-      p.sente = sente
-      p.promoted = false
-    else
-      @array[x1][y1] = @array[x0][y0]
-      @array[x0][y0] = nil
-      if (@array[x1][y1].name != name) # promoted ?
-        return false if (@array[x1][y1].promoted_name != name) # can't promote
-        @array[x1][y1].promoted = true
-      end
+      return :illegal if (! have_piece?(hands, name))
+    elsif (! @array[x0][y0])
+      return :illegal           # no piece
+    elsif (@array[x0][y0].sente != sente)
+      return :illegal           # this is not mine
+    elsif (@array[x0][y0].name != name)
+      return :illegal if (@array[x0][y0].promoted_name != name) # can't promote
+    end
+
+    ## destination check
+    if (@array[x1][y1] &&
+        (@array[x1][y1].sente == sente)) # can't capture mine
+      return :illegal
+    elsif ((x0 == 0) && (y0 == 0) && @array[x1][y1])
+      return :illegal           # can't put on existing piece
+    end
+
+    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.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 :uchifuzume
     end
     end
-    return true                 # legal move
+
+    move_to(x0, y0, x1, y1, name, sente)
+
+    update_sennichite(sente)
+    return :normal
   end
 
   def to_s
   end
 
   def to_s
@@ -484,164 +1543,493 @@ class Board
       end
       a.push("\n")
     end
       end
       a.push("\n")
     end
+    a.push("%s\n" % [@teban ? "+" : "-"])
     return a.join
   end
 end
 
     return a.join
   end
 end
 
+class GameResult
+  attr_reader :players, :black, :white
+
+  def initialize(p1, p2)
+    @players = [p1, p2]
+    if p1.sente && !p2.sente
+      @black, @white = p1, p2
+    elsif !p1.sente && p2.sente
+      @black, @white = p2, p1
+    else
+      raise "Never reached!"
+    end
+  end
+end
+
+class GameResultWin < GameResult
+  attr_reader :winner, :loser
+
+  def initialize(winner, loser)
+    super
+    @winner, @loser = winner, loser
+    @winner.last_game_win = true
+    @loser.last_game_win  = false
+  end
+
+  def to_s
+    black_name = @black.id || @black.name
+    white_name = @white.id || @white.name
+    "%s:%s" % [black_name, white_name]
+  end
+end
+
+class GameResultDraw < GameResult
+  def initialize(p1, p2)
+    super
+    p1.last_game_win = false
+    p2.last_game_win = false
+  end
+end
+
 class Game
 class Game
+  @@mutex = Mutex.new
+  @@time  = 0
+
   def initialize(game_name, player0, player1)
   def initialize(game_name, player0, player1)
+    @monitors = Array::new
     @game_name = game_name
     @game_name = game_name
+    if (@game_name =~ /-(\d+)-(\d+)$/)
+      @total_time = $1.to_i
+      @byoyomi = $2.to_i
+    end
+
     if (player0.sente)
     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"
 
     @sente.status = "agree_waiting"
-    @gote.status = "agree_waiting"
-    @id = sprintf("%s-%s-%s-%s", @game_name, @sente.name, @gote.name, Time::new.strftime("%Y%m%d%H%M%S"))
-    LEAGUE.games[@id] = self
+    @gote.status  = "agree_waiting"
 
 
+    @id = sprintf("%s+%s+%s+%s+%s", 
+                  LEAGUE.event, @game_name, 
+                  @sente.name, @gote.name, issue_current_time)
+    @logfile = File.join(LEAGUE.dir, @id + ".csa")
+
+    LEAGUE.games[@id] = self
 
 
-    log_message(sprintf("game created %s %s %s", game_name, sente.name, gote.name))
+    log_message(sprintf("game created %s", @id))
 
 
-    @logfile = @id + ".csa"
     @board = Board::new
     @board.initial
     @start_time = nil
     @fh = nil
     @board = Board::new
     @board.initial
     @start_time = nil
     @fh = nil
+    @result = nil
 
     propose
   end
 
     propose
   end
-  attr_accessor :game_name, :sente, :gote, :id, :board, :current_player, :next_player, :fh
+  attr_accessor :game_name, :total_time, :byoyomi, :sente, :gote, :id, :board, :current_player, :next_player, :fh, :monitors
+  attr_accessor :last_move, :current_turn
+  attr_reader   :result
+
+  def rated?
+    @sente.rated? && @gote.rated?
+  end
+
+  def turn?(player)
+    return player.status == "game" && @current_player == player
+  end
+
+  def monitoron(monitor)
+    @monitors.delete(monitor)
+    @monitors.push(monitor)
+  end
+
+  def monitoroff(monitor)
+    @monitors.delete(monitor)
+  end
+
+  def reject(rejector)
+    @sente.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
+    @gote.write_safe(sprintf("REJECT:%s by %s\n", @id, rejector))
+    finish
+  end
+
+  def kill(killer)
+    if ["agree_waiting", "start_waiting"].include?(@sente.status)
+      reject(killer.name)
+    elsif (@current_player == killer)
+      abnormal_lose()
+      finish
+    end
+  end
 
   def finish
 
   def finish
-    log_message(sprintf("game finished %s %s %s", game_name, sente.name, gote.name))
+    log_message(sprintf("game finished %s", @id))
     @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))    
     @fh.close
     @fh.printf("'$END_TIME:%s\n", Time::new.strftime("%Y/%m/%d %H:%M:%S"))    
     @fh.close
+
+    @sente.game = nil
+    @gote.game = nil
     @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 == LoginCSA::PROTOCOL)
+      @next_player.finish
+    end
+    @monitors = Array::new
+    @sente = nil
+    @gote = nil
+    @current_player = nil
+    @next_player = nil
     LEAGUE.games.delete(@id)
   end
 
     LEAGUE.games.delete(@id)
   end
 
+  # class Game
   def handle_one_move(str, player)
   def handle_one_move(str, player)
-    finish_flag = false
-    if (@current_player == player)
-      @end_time = Time::new
-      t = @end_time - @start_time
-      t = Least_Time_Per_Move if (t < Least_Time_Per_Move)
-      if (str != :timeout)
-        legal_move = @board.handle_one_move(str)
-        if (legal_move)
+    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
+    @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
+
+      move_status = @board.handle_one_move(str, @sente == @current_player)
+
+      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)
           @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)
-        else
-          @fh.printf("'ILLEGAL_MOVE(%s)\n", str)
+          @last_move = sprintf("%s,T%d", str, t)
+          @current_turn += 1
+        end
+
+        @monitors.each do |monitor|
+          monitor.write_safe(show.gsub(/^/, "##[MONITOR][#{@id}] "))
+          monitor.write_safe(sprintf("##[MONITOR][%s] +OK\n", @id))
         end
         end
-        @current_player.mytime = @current_player.mytime - t
-      else
-        @current_player.mytime = 0
-      end
-      if (!legal_move)
-        illegal_end()
-        finish_flag = true
-      elsif (@current_player.mytime <= 0)
-        timeout_end()
-        finish_flag = true
-      elsif (str =~ /%KACHI/)
-        kachi_end()
-        finish_flag = true
-      elsif (str =~ /%TORYO/)
-        toryo_end()
-        finish_flag = true
       end
       end
-      (@current_player, @next_player) = [@next_player, @current_player]
-      @start_time = Time::new
-      finish if (finish_flag)
-      return finish_flag
+    end
+
+    if (@next_player.status != "game") # rival is logout or disconnected
+      abnormal_win()
+    elsif (status == :timeout)
+      timeout_lose()
+    elsif (move_status == :illegal)
+      illegal_lose()
+    elsif (move_status == :kachi_win)
+      kachi_win()
+    elsif (move_status == :kachi_lose)
+      kachi_lose()
+    elsif (move_status == :toryo)
+      toryo_lose()
+    elsif (move_status == :outori)
+      outori_win()
+    elsif (move_status == :oute_sennichite_sente_lose)
+      oute_sennichite_win_lose(@gote, @sente) # Sente is checking
+    elsif (move_status == :oute_sennichite_gote_lose)
+      oute_sennichite_win_lose(@sente, @gote) # Gote is checking
+    elsif (move_status == :sennichite)
+      sennichite_draw()
+    elsif (move_status == :uchifuzume)
+      uchifuzume_lose()
+    elsif (move_status == :oute_kaihimore)
+      oute_kaihimore_lose()
+    else
+      finish_flag = false
+    end
+    finish() if finish_flag
+    @current_player, @next_player = @next_player, @current_player
+    @start_time = Time::new
+    return finish_flag
+  end
+
+  def abnormal_win
+    @current_player.status = "connected"
+    @next_player.status = "connected"
+    @current_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
+    @next_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
+    @fh.printf("%%TORYO\n")
+    @fh.print(@board.to_s.gsub(/^/, "\'"))
+    @fh.printf("'summary:abnormal:%s win:%s lose\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@current_player, @next_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
+    @monitors.each do |monitor|
+      monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
+    end
+  end
+
+  def abnormal_lose
+    @current_player.status = "connected"
+    @next_player.status = "connected"
+    @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
+    @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
+    @fh.printf("%%TORYO\n")
+    @fh.print(@board.to_s.gsub(/^/, "\'"))
+    @fh.printf("'summary:abnormal:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@next_player, @current_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
+    @monitors.each do |monitor|
+      monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
+    end
+  end
+
+  def sennichite_draw
+    @current_player.status = "connected"
+    @next_player.status = "connected"
+    @current_player.write_safe("#SENNICHITE\n#DRAW\n")
+    @next_player.write_safe("#SENNICHITE\n#DRAW\n")
+    @fh.print(@board.to_s.gsub(/^/, "\'"))
+    @fh.printf("'summary:sennichite:%s draw:%s draw\n", @current_player.name, @next_player.name)
+    @result = GameResultDraw.new(@current_player, @next_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
+    @monitors.each do |monitor|
+      monitor.write_safe(sprintf("##[MONITOR][%s] #SENNICHITE\n", @id))
+    end
+  end
+
+  def oute_sennichite_win_lose(winner, loser)
+    @current_player.status = "connected"
+    @next_player.status = "connected"
+    loser.write_safe("#OUTE_SENNICHITE\n#LOSE\n")
+    winner.write_safe("#OUTE_SENNICHITE\n#WIN\n")
+    @fh.print(@board.to_s.gsub(/^/, "\'"))
+    if loser == @current_player
+      @fh.printf("'summary:oute_sennichite:%s lose:%s win\n", @current_player.name, @next_player.name)
+    else
+      @fh.printf("'summary:oute_sennichite:%s win:%s lose\n", @current_player.name, @next_player.name)
+    end
+    @result = GameResultWin.new(winner, loser)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
+    @monitors.each do |monitor|
+      monitor.write_safe(sprintf("##[MONITOR][%s] #OUTE_SENNICHITE\n", @id))
+    end
+  end
+
+  def illegal_lose
+    @current_player.status = "connected"
+    @next_player.status = "connected"
+    @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
+    @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
+    @fh.print(@board.to_s.gsub(/^/, "\'"))
+    @fh.printf("'summary:illegal move:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@next_player, @current_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
+    @monitors.each do |monitor|
+      monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
+    end
+  end
+
+  def uchifuzume_lose
+    @current_player.status = "connected"
+    @next_player.status = "connected"
+    @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
+    @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
+    @fh.print(@board.to_s.gsub(/^/, "\'"))
+    @fh.printf("'summary:uchifuzume:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@next_player, @current_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
+    @monitors.each do |monitor|
+      monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
     end
   end
 
     end
   end
 
-  def illegal_end
+  def oute_kaihimore_lose
     @current_player.status = "connected"
     @next_player.status = "connected"
     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
     @current_player.status = "connected"
     @next_player.status = "connected"
     @current_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
     @next_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
+    @fh.print(@board.to_s.gsub(/^/, "\'"))
+    @fh.printf("'summary:oute_kaihimore:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@next_player, @current_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
+    @monitors.each do |monitor|
+      monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
+    end
   end
 
   end
 
-  def timeout_end
+  def timeout_lose
     @current_player.status = "connected"
     @next_player.status = "connected"
     @current_player.write_safe("#TIME_UP\n#LOSE\n")
     @next_player.write_safe("#TIME_UP\n#WIN\n")
     @current_player.status = "connected"
     @next_player.status = "connected"
     @current_player.write_safe("#TIME_UP\n#LOSE\n")
     @next_player.write_safe("#TIME_UP\n#WIN\n")
+    @fh.print(@board.to_s.gsub(/^/, "\'"))
+    @fh.printf("'summary:time up:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@next_player, @current_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
+    @monitors.each do |monitor|
+      monitor.write_safe(sprintf("##[MONITOR][%s] #TIME_UP\n", @id))
+    end
+  end
+
+  def kachi_win
+    @current_player.status = "connected"
+    @next_player.status = "connected"
+    @current_player.write_safe("%KACHI\n#JISHOGI\n#WIN\n")
+    @next_player.write_safe("%KACHI\n#JISHOGI\n#LOSE\n")
+    @fh.printf("%%KACHI\n")
+    @fh.print(@board.to_s.gsub(/^/, "\'"))
+    @fh.printf("'summary:kachi:%s win:%s lose\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@current_player, @next_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
+    @monitors.each do |monitor|
+      monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
+    end
+  end
+
+  def kachi_lose
+    @current_player.status = "connected"
+    @next_player.status = "connected"
+    @current_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#LOSE\n")
+    @next_player.write_safe("%KACHI\n#ILLEGAL_MOVE\n#WIN\n")
+    @fh.printf("%%KACHI\n")
+    @fh.print(@board.to_s.gsub(/^/, "\'"))
+    @fh.printf("'summary:illegal kachi:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@next_player, @current_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
+    @monitors.each do |monitor|
+      monitor.write_safe(sprintf("##[MONITOR][%s] %%KACHI\n", @id))
+    end
   end
 
   end
 
-  def kachi_end
+  def toryo_lose
     @current_player.status = "connected"
     @next_player.status = "connected"
     @current_player.status = "connected"
     @next_player.status = "connected"
-    @current_player.write_safe("#JISHOGI\n#WIN\n")
-    @next_player.write_safe("#JISHOGI\n#LOSE\n")
+    @current_player.write_safe("%TORYO\n#RESIGN\n#LOSE\n")
+    @next_player.write_safe("%TORYO\n#RESIGN\n#WIN\n")
+    @fh.printf("%%TORYO\n")
+    @fh.print(@board.to_s.gsub(/^/, "\'"))
+    @fh.printf("'summary:toryo:%s lose:%s win\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@next_player, @current_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
+    @monitors.each do |monitor|
+      monitor.write_safe(sprintf("##[MONITOR][%s] %%TORYO\n", @id))
+    end
   end
 
   end
 
-  def toryo_end
+  def outori_win
     @current_player.status = "connected"
     @next_player.status = "connected"
     @current_player.status = "connected"
     @next_player.status = "connected"
-    @current_player.write_safe("#RESIGN\n#LOSE\n")
-    @next_player.write_safe("#RESIGN\n#WIN\n")
+    @current_player.write_safe("#ILLEGAL_MOVE\n#WIN\n")
+    @next_player.write_safe("#ILLEGAL_MOVE\n#LOSE\n")
+    @fh.print(@board.to_s.gsub(/^/, "\'"))
+    @fh.printf("'summary:outori:%s win:%s lose\n", @current_player.name, @next_player.name)
+    @result = GameResultWin.new(@current_player, @next_player)
+    @fh.printf("'rating:#{@result.to_s}\n") if rated?
+    @monitors.each do |monitor|
+      monitor.write_safe(sprintf("##[MONITOR][%s] #ILLEGAL_MOVE\n", @id))
+    end
   end
 
   def start
   end
 
   def start
-    log_message(sprintf("game started %s %s %s", game_name, sente.name, gote.name))
+    log_message(sprintf("game started %s", @id))
     @sente.write_safe(sprintf("START:%s\n", @id))
     @gote.write_safe(sprintf("START:%s\n", @id))
     @sente.write_safe(sprintf("START:%s\n", @id))
     @gote.write_safe(sprintf("START:%s\n", @id))
-    @mytime = Total_Time    
+    @sente.mytime = @total_time
+    @gote.mytime = @total_time
     @start_time = Time::new
   end
 
   def propose
     @start_time = Time::new
   end
 
   def propose
-    begin
-      @fh = open(@logfile, "w")
-      @fh.sync = true
+    @fh = open(@logfile, "w")
+    @fh.sync = true
 
 
-      @fh.printf("V2\n")
-      @fh.printf("N+%s\n", @sente.name)
-      @fh.printf("N-%s\n", @gote.name)
-      @fh.printf("$EVENT:%s\n", @id)
+    @fh.puts("V2")
+    @fh.puts("N+#{@sente.name}")
+    @fh.puts("N-#{@gote.name}")
+    @fh.puts("$EVENT:#{@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
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
-P2 * -HI *  *  *  *  * -KA *
+P2 * -HI *  *  *  *  * -KA * 
 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
-P4 *  *  *  *  *  *  *  *  *
-P5 *  *  *  *  *  *  *  *  *
-P6 *  *  *  *  *  *  *  *  *
+P4 *  *  *  *  *  *  *  *  * 
+P5 *  *  *  *  *  *  *  *  * 
+P6 *  *  *  *  *  *  *  *  * 
 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
-P8 * +KA *  *  *  *  * +HI *
+P8 * +KA *  *  *  *  * +HI * 
 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
 +
 EOM
 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
 +
 EOM
-    end
+  end
+
+  def show()
+    str0 = <<EOM
+BEGIN Game_Summary
+Protocol_Version:1.1
+Protocol_Mode:Server
+Format:Shogi 1.0
+Declaration:Jishogi 1.1
+Game_ID:#{@id}
+Name+:#{@sente.name}
+Name-:#{@gote.name}
+Rematch_On_Draw:NO
+To_Move:+
+BEGIN Time
+Time_Unit:1sec
+Total_Time:#{@total_time}
+Byoyomi:#{@byoyomi}
+Least_Time_Per_Move:#{Least_Time_Per_Move}
+Remaining_Time+:#{@sente.mytime}
+Remaining_Time-:#{@gote.mytime}
+Last_Move:#{@last_move}
+Current_Turn:#{@current_turn}
+END Time
+BEGIN Position
+EOM
+
+    str1 = <<EOM
+END Position
+END Game_Summary
+EOM
+
+    return str0 + @board.to_s + str1
   end
 
   def propose_message(sg_flag)
     str = <<EOM
   end
 
   def propose_message(sg_flag)
     str = <<EOM
+BEGIN Game_Summary
+Protocol_Version:1.1
 Protocol_Mode:Server
 Format:Shogi 1.0
 Protocol_Mode:Server
 Format:Shogi 1.0
+Declaration:Jishogi 1.1
 Game_ID:#{@id}
 Name+:#{@sente.name}
 Name-:#{@gote.name}
 Game_ID:#{@id}
 Name+:#{@sente.name}
 Name-:#{@gote.name}
@@ -650,19 +2038,19 @@ Rematch_On_Draw:NO
 To_Move:+
 BEGIN Time
 Time_Unit:1sec
 To_Move:+
 BEGIN Time
 Time_Unit:1sec
-Total_Time:#{Total_Time}
+Total_Time:#{@total_time}
+Byoyomi:#{@byoyomi}
 Least_Time_Per_Move:#{Least_Time_Per_Move}
 END Time
 BEGIN Position
 Least_Time_Per_Move:#{Least_Time_Per_Move}
 END Time
 BEGIN Position
-Jishogi_Declaration:1.1
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
 P1-KY-KE-GI-KI-OU-KI-GI-KE-KY
-P2 * -HI *  *  *  *  * -KA *
+P2 * -HI *  *  *  *  * -KA * 
 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
 P3-FU-FU-FU-FU-FU-FU-FU-FU-FU
-P4 *  *  *  *  *  *  *  *  *
-P5 *  *  *  *  *  *  *  *  *
-P6 *  *  *  *  *  *  *  *  *
+P4 *  *  *  *  *  *  *  *  * 
+P5 *  *  *  *  *  *  *  *  * 
+P6 *  *  *  *  *  *  *  *  * 
 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
 P7+FU+FU+FU+FU+FU+FU+FU+FU+FU
-P8 * +KA *  *  *  *  * +HI *
+P8 * +KA *  *  *  *  * +HI * 
 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
 P+
 P-
 P9+KY+KE+GI+KI+OU+KI+GI+KE+KY
 P+
 P-
@@ -672,7 +2060,24 @@ END Game_Summary
 EOM
     return str
   end
 EOM
     return str
   end
+  
+  private
+  
+  def issue_current_time
+    time = Time::new.strftime("%Y%m%d%H%M%S").to_i
+    @@mutex.synchronize do
+      while time <= @@time do
+        time += 1
+      end
+      @@time = time
+    end
+  end
 end
 end
+end # module ShogiServer
+
+#################################################
+# MAIN
+#
 
 def usage
     print <<EOM
 
 def usage
     print <<EOM
@@ -680,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
@@ -688,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
@@ -695,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 message: %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|
@@ -735,82 +2144,149 @@ def parse_command_line
   return options
 end
 
   return options
 end
 
-LEAGUE = League::new
-
-def good_login?(str)
-  return false if (str !~ /^LOGIN /)
-  tokens = str.split
-  if ((tokens.length == 3) || 
-      ((tokens.length == 4) && tokens[3] == "x1"))
-    ## ok
-  else
-    return false
+def write_pid_file(file)
+  open(file, "w") do |fh|
+    fh.puts "#{$$}"
   end
   end
-  return true
 end
 
 end
 
-def  write_pid_file(file)
-  open(file, "w") do |fh|
-    fh.print Process::pid, "\n"
+def mutex_watchdog(mutex, sec)
+  while true
+    begin
+      timeout(sec) do
+        begin
+          mutex.lock
+        ensure
+          mutex.unlock
+        end
+      end
+      sleep(sec)
+    rescue TimeoutError
+      log_error("mutex watchdog timeout")
+      exit(1)
+    end
   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
   $mutex = Mutex::new
+  Thread::start do
+    Thread.pass
+    mutex_watchdog($mutex, 10)
+  end
+
   $options = parse_command_line
   if (ARGV.length != 2)
     usage
     exit 2
   end
   $options = parse_command_line
   if (ARGV.length != 2)
     usage
     exit 2
   end
-  event = ARGV.shift
-  port = ARGV.shift
 
 
-  write_pid_file($options["pid-file"]) if ($options["pid-file"])
+  LEAGUE.event = ARGV.shift
+  port = ARGV.shift
 
 
+  dir = $options["daemon"]
+  dir = File.expand_path(dir) if dir
+  if dir && ! File.exist?(dir)
+    FileUtils.mkdir(dir)
+  end
+  log_file = dir ? File.join(dir, "shogi-server.log") : STDOUT
+  $logger = WEBrick::Log.new(log_file) # thread safe
+
+  LEAGUE.dir = dir || File.dirname(__FILE__)
+  LEAGUE.setup_players_database
+
+  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
 
 
-  Thread.abort_on_exception = true
+  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 = TCPserver.open(port)
-  log_message("server started")
+  server.start do |client|
+      # client.sync = true # this is already set in WEBrick 
+      client.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
+        # Keepalive time can be set by /proc/sys/net/ipv4/tcp_keepalive_time
+      player, login = login_loop(client) # loop
+      next unless player
 
 
-  while true
-    Thread::start(server.accept) do |client|
-      client.sync = true
-      player = nil
-      while (str = client.gets_timeout(Login_Time))
-        begin
-          $mutex.lock
-          Thread::kill(Thread::current) if (! str) # disconnected
-          str =~ /([\r\n]*)$/
-          eol = $1
-          if (good_login?(str))
-            player = Player::new(str, client)
-            if (LEAGUE.duplicated?(player))
-              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::kill(Thread::current)
-            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)
-            client.close
-            Thread::kill(Thread::current)
-          end
-        ensure
-          $mutex.unlock
-        end
-      end                       # login loop
       log_message(sprintf("user %s login", player.name))
       log_message(sprintf("user %s login", player.name))
-      player.run
-      LEAGUE.delete(player)
-      log_message(sprintf("user %s logout", player.name))
-    end
+      login.process
+      player.run(login.csa_1st_str) # loop
+      begin
+        $mutex.lock
+        if (player.game)
+          player.game.kill(player)
+        end
+        player.finish # socket has been closed
+        LEAGUE.delete(player)
+        log_message(sprintf("user %s logout", player.name))
+      ensure
+        $mutex.unlock
+      end
   end
 end
 
   end
 end
 
+
 if ($0 == __FILE__)
 if ($0 == __FILE__)
+  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